appimage/README.md
Builds a single relocatable PlotJuggler-<version>-<arch>.AppImage bundling the
plotjuggler4 shell, its Qt 6 + Conan runtime, and (optionally) a set of plugins.
The default app version, Qt version, and AppImage arch come from repo-root
versions.env; release builds may override PJ_VERSION with the tag.
./install_qt6.sh # once: Qt into ./.qt
./build.sh # builds build/pj_app/plotjuggler4 + Conan runtime env
appimage/build_appimage.sh # app-only AppImage
appimage/build_appimage.sh --plugins-dir <path> # bundle a local plugin folder
appimage/build_appimage.sh --plugins-registry # bundle the official set (CI default)
Output lands at appimage/PlotJuggler-<version>-<arch>.AppImage.
Plugins are not part of this repo. They are built and published separately by
pj-official-plugins as
self-contained, per-extension marketplace zips (each = the plugin .so + its
manifest.json, with heavy dependencies static-linked), and indexed for download
by the pj-plugin-registry.
Because the .sos are self-contained, bundling is just copy/unpack — no
dependency deployment.
There are two ways to bundle them (and a no-plugin default):
--plugins-dir <path> — copy a folder you curated, verbatim, into
usr/lib/plotjuggler/plugins/. Use this for local/offline builds; the folder
must contain ready-to-run (self-contained) plugins, e.g. unpacked marketplace
zips, each in its own subdirectory.--plugins-registry [url] — download the curated set (BUNDLE_IDS in
build_appimage.sh) from the plugin registry, verify each sha256 checksum,
and unpack into usr/lib/plotjuggler/plugins/<id>/. Defaults to the registry's
development branch (its default branch — the same ref the Windows installer
uses); pass a URL to pin a specific ref. This is what the release CI uses.--plugin-dir.--plugins-registry)The registry lists every official extension; the AppImage bundles this subset
(BUNDLE_IDS — same set as the Windows installer's $PluginIds, plus the
Linux-only ros2-topic-subscriber):
csv-loader, mcap-loader, parquet-loader, ulog-loader, mp4-loader,
pointcloud-3d-loader, dummy-streamer, foxglove-bridge,
plotjuggler-bridge, webrtc-client, ros-parser, protobuf-parser,
json-parser, data-tamer-parser, toolbox-quaternion,
toolbox-transform-editor, toolbox-mosaico, ros2-topic-subscriber.
Not bundled: toolbox-colormap, toolbox-reactive-scripts-editor (excluded by
request); toolbox-fft, mqtt-subscriber, udp-server, zmq-subscriber,
lerobot-loader (published, but not curated — installable via the in-app
marketplace).
Bundled plugins go under usr/lib/plotjuggler/plugins/<id>/ — the FHS-correct
bucket for arch-dependent .so code, and the exact path the installed
plotjuggler4 resolves relative to itself (usr/bin/plotjuggler4 →
../lib/plotjuggler/plugins). The app never loads plugins from there directly:
at startup it seeds that dir into the writable per-user extensions dir
(copying new ids, refreshing ids whose bundled version is newer) and loads
everything from the extensions dir — so marketplace install/uninstall works
normally and the read-only bundle stays a seed source. Kept out of
usr/plugins/ so the seed's recursive plugin scan does not collide with Qt's
own platform plugins that linuxdeploy-plugin-qt deploys there. AppRun.sh
does not pass --plugin-dir; that flag stays a user-facing option,
forwarded verbatim if the user supplies one.
appimage/build_in_docker.sh builds the AppImage in the fully-baked builder
image (Dockerfile.build). Plugins:
--plugins-registry — bundle the official set from the plugin registry.--plugins-dir <dir> — <dir> may be a pj-official-plugins source repo
(compiled inside the builder at the container's glibc 2.35, so the .so
actually dlopen on the runtime image / older distros — host-built plugins
link a newer glibc and silently fail to load), or a directory of prebuilt
self-contained plugins (copied verbatim). A source build runs the single
aggregate ./build.sh, which on Linux compiles all plugins including
toolbox_mosaico in one pass (Arrow built once with Flight + gRPC), so Mosaico
is included without a second standalone build. Any host path works — it is
bind-mounted for you, no manual staging.appimage/run_in_docker.sh [--help] verifies the built AppImage on a clean
ubuntu:22.04 runtime image (Dockerfile.run) with only base X/GL libraries;
the GUI is forwarded to the host display via xhost.
.github/workflows/linux-appimage-release.yml (the release build) compiles the
app inside the pj4-appimage-builder container (glibc 2.35 floor), bundles the
curated plugin set with --plugins-registry, audits the result's glibc needs,
and — on a version tag (e.g. 3.9.1, no leading v) — attaches the AppImage to the GitHub Release
(workflow_dispatch builds an artifact only).
A single AppImage supports multiple ROS 2 distros (humble / iron / jazzy /
rolling) through the ros2-topic-subscriber extension. pj-official-plugins
builds it as one linux-x86_64 zip: a distro-agnostic proxy (links no ROS,
self-describing so plugin discovery works on machines with no ROS installed)
plus per-distro inner libraries under dist/<distro>/. At runtime the proxy
detects the user's distro ($ROS_DISTRO → /opt/ros/<distro> →
$CONDA_PREFIX/share/<distro>) and dlopens the matching inner, which binds to
the user's sourced system ROS (the inners are intentionally not
self-contained — they must speak to the live ROS graph).
The AppImage needs no special handling for this: registry-mode unpacks the zip
into usr/lib/plotjuggler/plugins/<id>/ verbatim, preserving the
dist/<distro>/ layout the proxy resolves relative to itself. The release CI's
glibc audit exempts the per-distro inners (they are built in each distro's own
container and only ever dlopen-ed on machines running that distro).
PJ3 linked the system Qt 5, so linuxdeploy-plugin-qt found Qt on system paths.
PJ4 links Qt from ./.qt and most external dependencies from Conan, so the build
script points linuxdeploy at both (QMAKE, PATH, and sourcing
build/conanrun.sh for the Conan library closure).
plotjuggler4; a plugin needing a Qt module that the
app does not pull would be missed. None observed yet; revisit if a plugin
fails to load with a missing-libQt6* error.