Back to Plotjuggler

PlotJuggler 4 — AppImage packaging

appimage/README.md

3.999.36.9 KB
Original Source

PlotJuggler 4 — AppImage packaging

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.

Build

bash
./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

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.
  • (no flag) — app-only AppImage. Users add plugins later via the in-app marketplace or --plugin-dir.

Curated set (--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).

Where plugins live, and why

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.

Build & verify in Docker

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.

CI

.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).

Multi-distro ROS 2

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).

How it differs from PlotJuggler 3

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).

Known limitations

  1. Qt-module coverage for plugins is not separately analyzed. The Qt plugin deploys modules based on 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.