Files
emulator/AppImageBuilder/package-citron-linux.sh
T
cheezwiz7899 f41ae5a044 build(linux): fix packaging bugs, deps, and aarch64 regressions
package-citron-linux.sh:
- Guard patchelf with || true so readelf fallback is reachable under set -e
- Check for zstd binary before tar --zstd, fail with a clear message if missing
- Fix qt.conf Translations path to match actual AppDir layout (lib/qt6/translations)

build-citron-linux.sh:
- Split optional linux-tools packages out of the required apt install block
- Add zstd CLI to all distro setup functions
- Add libXext-devel to zypper X11 block (required by SDL2 CheckX11)
- Always apply patchelf --force-rpath to ensure DT_RUNPATH is never left in place
- Fix Qt translations find to drop hardcoded gcc_64, works for aarch64 now
- Fail fast when CITRON_QT_PATH is empty rather than silently using system Qt
2026-07-02 13:23:53 +10:00

411 lines
21 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/sh
# SPDX-FileCopyrightText: 2026 citron Emulator Project
# SPDX-License-Identifier: GPL-3.0-or-later
#
# package-citron-linux.sh — Build AppImage + tar.zst using pkgforge tooling.
#
# Called by build-citron-linux.sh's build_appimage_stage() after the install
# tree has been staged under build_dir/install-root.
# Can also be invoked directly if the install tree is already in place.
#
# Environment variables:
# APP_VERSION Version string embedded in artifact filenames (required)
# ARCH CPU architecture (default: uname -m)
# ARCH_SUFFIX Extra suffix appended to filenames (e.g. _v3)
# DEVEL Set to 'true' to rename app to "citron nightly" in AppImage
# OUTPATH Output directory for finished artifacts (default: $PWD/dist)
# DESTDIR Staging root prepended to /usr paths when locating the
# citron binary, desktop file, icon, and Qt translations
# (default: "" — real /usr, e.g. for an already-installed
# system citron). Set by build-citron-linux.sh's
# build_appimage_stage() to a build-tree staging directory so
# no system files are read from /usr and nothing is written
# there. Does NOT apply to libpulse/libgamemode — those are
# genuine host runtime libraries, not part of citron's install.
# STRACE_MODE Enable dlopen/LD_DEBUG scan when citron starts up (default: 0)
# Set to 1 only if xvfb-run is installed; see comment below.
# DEPLOY_VULKAN Deploy Mesa/Vulkan DRI driver collection (default: 0)
# DEPLOY_OPENGL Deploy OpenGL/EGL/GLX libs into AppImage (default: 0)
# DEPLOY_PIPEWIRE Deploy PipeWire SPA/ALSA plugin trees (default: 0)
# DEPLOY_GTK Deploy GTK modules and gvfs into AppImage (default: 0)
# CITRON_QT_PATH Qt6 installation prefix (= QT_TARGET_PATH from CMake,
# e.g. .../cpmcache/qt-bin/6.9.3/gcc_64). Set automatically
# by build-citron-linux.sh. Used to derive QT_LOCATION so
# quick-sharun bundles CPM Qt plugins, not system Qt plugins.
set -ex
ARCH="${ARCH:-$(uname -m)}"
if [ -z "${APP_VERSION:-}" ]; then
echo "Error: APP_VERSION environment variable is not set." >&2
exit 1
fi
DESTDIR="${DESTDIR:-}"
OUTNAME_BASE="citron_nightly-${APP_VERSION}-linux-${ARCH}${ARCH_SUFFIX:-}"
OUTPATH="${OUTPATH:-$PWD/dist}"
export OUTNAME="${OUTNAME_BASE}.AppImage"
export DESKTOP="${DESTDIR}/usr/share/applications/org.citron_emu.citron.desktop"
# Prefer a rasterised 256×256 PNG for .DirIcon — SVG fails silently as an
# AppImage icon on most desktop environments (appimaged, KDE, GNOME all expect
# a PNG for taskbar/launcher display).
#
# Priority:
# 1. Pre-built PNG installed by cmake (dist/org.citron_emu.citron.png →
# share/icons/hicolor/256x256/apps/); commit this to the emulator repo
# to make it permanent without needing any conversion tool.
# 2. Convert the installed SVG if a conversion tool is present.
# 3. Fall back to the raw SVG (icon will be broken on most desktops).
_png_from_repo="${DESTDIR}/usr/share/icons/hicolor/256x256/apps/org.citron_emu.citron.png"
_svg_icon="${DESTDIR}/usr/share/icons/hicolor/scalable/apps/org.citron_emu.citron.svg"
_png_icon="/tmp/citron-diricon-$$.png"
_try_svg2png() {
if command -v rsvg-convert >/dev/null 2>&1; then
rsvg-convert -w 256 -h 256 "$1" -o "$2" 2>/dev/null && return 0
fi
if command -v inkscape >/dev/null 2>&1; then
inkscape --export-filename="$2" --export-width=256 --export-height=256 \
"$1" >/dev/null 2>&1 && return 0
fi
if command -v convert >/dev/null 2>&1; then
convert -background none -size 256x256 "$1" "$2" 2>/dev/null && return 0
fi
if command -v magick >/dev/null 2>&1; then
magick -background none -size 256x256 "$1" "$2" 2>/dev/null && return 0
fi
return 1
}
if [ -f "$_png_from_repo" ]; then
export ICON="$_png_from_repo"
elif [ -f "$_svg_icon" ] && _try_svg2png "$_svg_icon" "$_png_icon"; then
export ICON="$_png_icon"
else
export ICON="$_svg_icon"
fi
# ── quick-sharun deployment flags ───────────────────────────────────────────
#
# STRACE_MODE=0 — disable the dlopen / LD_DEBUG scan entirely.
#
# In default mode (STRACE_MODE=1) quick-sharun spawns citron under
# LD_DEBUG=libs for STRACE_TIME (default 5 s), then kills it, to capture
# libs only dlopen'd at runtime. When xvfb-run is NOT installed, citron
# opens a real display window and the user must close it manually for the
# build to continue. Even with xvfb-run the scan is harmful for citron:
# everything it captures is stuff we explicitly do NOT want bundled —
# Vulkan ICD drivers (libvulkan_radeon.so, libvulkan_intel.so,
# libLLVM.so.20.1 at 137 MB, …) loaded by vkCreateInstance(), the full
# PipeWire SPA plugin tree loaded by PulseAudio's backend discovery, and
# GTK accessibility modules from the GNOME a11y bus. All of citron's
# legitimate runtime deps are already covered by the static ldd scan
# (explicit Qt plugins + libpulse + libgamemode).
export STRACE_MODE="${STRACE_MODE:-0}"
#
# DEPLOY_VULKAN=0 / DEPLOY_OPENGL=0 — do not stage Mesa DRI or the OpenGL set.
# NOTE: DEPLOY_VULKAN=0 does NOT suppress Vulkan ICDs captured by the dlopen
# scan (that requires STRACE_MODE=0 above); it only suppresses the separate
# static DEPLOY_VULKAN staging block (libVkLayer_*.so, etc.).
# libvulkan.so.1, libgbm.so, libdrm.so, libxcb-dri3.so, libxcb-glx.so are
# all DT_NEEDED of citron / Qt plugins and are captured by ldd regardless.
# SHARUN_ALLOW_SYS_VK_ICD=1 (set in .env below) makes sharun prefer the
# host GPU ICD at runtime, so we never need to bundle GPU-vendor drivers.
export DEPLOY_VULKAN="${DEPLOY_VULKAN:-0}"
export DEPLOY_OPENGL="${DEPLOY_OPENGL:-0}"
#
# DEPLOY_PIPEWIRE=0 — suppress the PipeWire/SPA/ALSA plugin staging block.
# On Ubuntu 24.04 PulseAudio is a PipeWire shim: ldd libpulse.so.0 shows
# libpipewire-0.3.so.0. quick-sharun's NEEDED_LIBS scan therefore sets
# DEPLOY_PIPEWIRE=1, staging pipewire-0.3/* + spa-0.2/**/* + alsa-lib/
# *pipewire* (~41 PipeWire plugins + 11 ALSA plugins + Bluetooth codecs).
# These are backend-resolution plugins resolved from the HOST at runtime;
# bundling them bloats the AppImage and causes host-version conflicts.
# We carry libpulse.so.0 itself (passed explicitly below) — that is enough.
export DEPLOY_PIPEWIRE="${DEPLOY_PIPEWIRE:-0}"
#
# DEPLOY_GTK=0 — suppress GTK module / gvfs staging.
# quick-sharun includes libqgtk3.so in the Qt plugin glob and runs ldd on
# it; ldd sees libgtk-3.so.0, triggering DEPLOY_GTK=1 which stages
# gtk-3.0/immodules/*, gvfs/libgvfscommon.so, and gio/modules/* (the full
# GTK accessibility + filesystem integration stack). quick-sharun already
# sets QUICK_SHARUN_SKIP_DEPS_FOR="libqgtk3.so" so lib4bin skips transitive
# deps of that plugin — but the DETECTION loop fires before SKIP applies and
# sets DEPLOY_GTK=1 anyway. The GTK platform theme should dlopen the HOST
# GTK at runtime; bundling it causes version conflicts.
export DEPLOY_GTK="${DEPLOY_GTK:-0}"
# Tell quick-sharun to use CPM's Qt installation for plugins, not the SYSTEM
# Qt. Without this, quick-sharun defaults to $LIB_DIR/qt6/plugins (system Qt),
# whose shared libraries depend on the SYSTEM libQt6Core.so.6. That system
# version may be older than the CPM-built Qt 6.9.3 citron was compiled against,
# causing at AppImage runtime:
# libQt6Core.so.6: version 'Qt_6.9' not found
#
# CITRON_QT_PATH = QT_TARGET_PATH from CMake = the Qt6 prefix directory (e.g.
# .../cpmcache/qt-bin/6.9.3/gcc_64). quick-sharun uses QT_LOCATION as the
# prefix and looks for plugins at $QT_LOCATION/plugins — that's exactly where
# aqt installs them.
if [ -n "${CITRON_QT_PATH:-}" ] && [ -z "${QT_LOCATION:-}" ]; then
export QT_LOCATION="${CITRON_QT_PATH}"
fi
# Fix for "libQt6Core.so.6: version 'Qt_6.9' not found" at AppImage runtime.
#
# Root cause: quick-sharun COPIES each CPM Qt plugin into AppDir/shared/lib/
# before running ldd on the copy. The plugins carry a $ORIGIN-relative
# DT_RUNPATH (e.g. "$ORIGIN/../../../lib") that is correct when the plugin is
# in $QT_LOCATION/plugins/platforms/, but resolves to a nonsense path once
# $ORIGIN becomes AppDir/shared/lib/. ldd then falls through to the system
# linker search path and resolves libQt6Core.so.6 to the SYSTEM Qt (e.g.
# Qt 6.4.2 on Ubuntu 24.04). That system copy is staged into the AppImage as
# AppDir/shared/lib/libQt6Core.so.6. At runtime the CPM Qt 6.9 plugins
# require the Qt_6.9 version symbol — absent from the system 6.4 core — and
# the AppImage aborts with the version error.
#
# Fix: prepend CPM Qt's lib dir to LD_LIBRARY_PATH before calling quick-sharun.
# LD_LIBRARY_PATH has higher priority than DT_RUNPATH, so every ldd invocation
# inside lib4bin now finds CPM libQt6Core.so.6 (and all other Qt libs)
# regardless of broken $ORIGIN resolution. The citron binary itself is
# unaffected: it carries DT_RPATH (set by _patch_binary_rpaths via
# patchelf --force-rpath), which has higher priority than LD_LIBRARY_PATH.
if [ -n "${CITRON_QT_PATH:-}" ]; then
export LD_LIBRARY_PATH="${CITRON_QT_PATH}/lib${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}"
fi
# Use the vendored quick-sharun.sh committed alongside this script rather than
# downloading from pkgforge HEAD. The vendored copy has appimagetool pinned to
# 0.3.2 and sharun pinned to 1.0.0 (both already in the file); all DEPLOY_*
# overrides and cleanup rules in this script were calibrated against that
# specific version. Pulling HEAD risks silent regressions if pkgforge changes
# DEPLOY_* defaults or the AppDir layout between releases.
_qs_src="$(cd "$(dirname "$0")" && pwd)/quick-sharun.sh"
if [ ! -f "${_qs_src}" ]; then
printf 'ERROR: vendored quick-sharun.sh not found at %s\n' "${_qs_src}" >&2
printf 'Commit quick-sharun.sh to the repo alongside this script.\n' >&2
exit 1
fi
cp "${_qs_src}" ./quick-sharun
chmod +x ./quick-sharun
# Bundle binary + optional runtime libs.
#
# gamemode and libpulse are dlopen'd by cubeb/citron at runtime rather than
# linked at build time, so quick-sharun's automatic dependency scan won't
# find them — they must be passed explicitly if present.
#
# Locate them via `ldconfig -p` rather than a hardcoded /usr/lib/*.so* glob:
# on multiarch systems (Debian/Ubuntu) these libs live under
# /usr/lib/<triplet>/, not /usr/lib/ directly. A glob that matches nothing is
# passed to quick-sharun as a literal non-existent path by POSIX sh (no
# nullglob), which quick-sharun then treats as a hard requirement and aborts.
# Neither lib is guaranteed to be present (e.g. pure PipeWire setups have no
# libpulse.so), so both are best-effort.
EXTRA_LIBS=""
GAMEMODE_LIB="$(ldconfig -p 2>/dev/null | awk '/libgamemode\.so/ {print $NF; exit}')"
if [ -n "$GAMEMODE_LIB" ]; then
EXTRA_LIBS="$EXTRA_LIBS $GAMEMODE_LIB"
fi
PULSE_LIB="$(ldconfig -p 2>/dev/null | awk '/libpulse\.so/ {print $NF; exit}')"
if [ -n "$PULSE_LIB" ]; then
EXTRA_LIBS="$EXTRA_LIBS $PULSE_LIB"
fi
# shellcheck disable=SC2086
./quick-sharun "${DESTDIR}/usr/bin/citron"* $EXTRA_LIBS
# Qt translations: do NOT copy manually here.
# quick-sharun's --make-appimage (lib4bin) already copies
# /usr/share/qt6/translations → AppDir/lib/qt6/translations/ automatically
# when it detects Qt. Adding a second copy from install-root would produce
# two separate translation trees (AppDir/usr/share/qt6/ and
# AppDir/lib/qt6/) wasting ~100 MB in the AppImage with no benefit.
# The qt.conf written below points Translations to the one quick-sharun stages.
# ── Post-dep-scan cleanup ────────────────────────────────────────────────────
# Belt-and-suspenders: remove any bloat that slipped past the DEPLOY_* flags
# or the STRACE_MODE=0 suppression (e.g. from ldd transitive chains we don't
# fully control, or from a future quick-sharun version that overrides our flags
# differently). This runs BEFORE --make-appimage so lib4bin never sees them.
#
# Pattern Why excluded
# libvulkan_*.so Mesa Vulkan ICD drivers: system-specific, huge.
# SHARUN_ALLOW_SYS_VK_ICD=1 uses host GPU driver.
# libLLVM*.so Mesa LLVM backend (137 MB): only needed by Mesa ICDs.
# vulkan/icd.d/* ICD manifest JSONs: useless without the ICD .so files.
# vulkan/implicit_layer.d/* Vulkan validation / overlay layers: dev tooling only.
# pipewire-*/ PipeWire backend plugins: resolved from host at runtime.
# spa-*/ PipeWire SPA plugins: same.
# alsa-lib/ ALSA plugins: same.
# gconv/ glibc character-set converters: citron never calls iconv.
_appdir="${PWD}/AppDir"
# Explicitly stage ld-linux into AppDir/shared/lib/ — lib4bin's AppRun.lib
# prints "Interpreter not found!" and aborts if this file is absent.
#
# sharun -g (called by quick-sharun) is supposed to copy it, but it reads the
# interpreter path from AppDir/shared/bin/citron AFTER _patch_away_usr_lib_dir
# has run sed -i on the binary. GNU sed on an ELF can silently corrupt the
# section that encodes PT_INTERP, causing sharun -g to find no interpreter and
# skip the copy. We do it here from the original install-root binary (never
# touched by sed) so it is guaranteed present regardless of sharun -g's outcome.
_interp=$(patchelf --print-interpreter "${DESTDIR}/usr/bin/citron" 2>/dev/null || true)
if [ -z "${_interp}" ]; then
# patchelf fallback: parse readelf output
_interp=$(readelf -l "${DESTDIR}/usr/bin/citron" 2>/dev/null \
| awk '/\[Requesting program interpreter:/{gsub(/[][]/,""); print $NF}')
fi
if [ -n "${_interp}" ] && [ -f "${_interp}" ]; then
mkdir -p "${_appdir}/shared/lib"
cp -L "${_interp}" "${_appdir}/shared/lib/${_interp##*/}"
printf 'Staged interpreter: %s\n' "${_interp##*/}"
else
printf 'WARNING: could not determine PT_INTERP of citron; AppImage may fail with "Interpreter not found!"\n' >&2
fi
find "${_appdir}" -name 'libvulkan_*.so' -delete 2>/dev/null || true
find "${_appdir}" -name 'libLLVM*.so*' -delete 2>/dev/null || true
find "${_appdir}" -path '*/vulkan/icd.d/*' -delete 2>/dev/null || true
find "${_appdir}" -path '*/vulkan/implicit_layer.d/*' -delete 2>/dev/null || true
find "${_appdir}" -path '*/pipewire-*/*' -type f -delete 2>/dev/null || true
find "${_appdir}" -path '*/spa-*/*' -type f -delete 2>/dev/null || true
find "${_appdir}" -path '*/alsa-lib/*' -type f -delete 2>/dev/null || true
find "${_appdir}" -path '*/gconv/*' -type f -delete 2>/dev/null || true
# Remove system XCB libs that duplicate the CPM xcb-build copies.
#
# citron's DT_RPATH (set by _patch_binary_rpaths) points to xcb-build before
# any system path. lib4bin strips those RPATHs, so at runtime all resolution
# goes through lib.path. The Qt XCB plugin (libqxcb.so from CPM Qt) pulls in
# system libxcb.so.1, libXau.so.6, and libXdmcp.so.6 via its own ldd chain,
# staging them flat in AppDir/lib/. The xcb-build copies of the same three
# libraries are already in AppDir at the absolute-mirrored path; keeping both
# is dead weight and violates the "CPM over system" policy.
#
# -maxdepth 1 on AppDir/lib/ hits only the flat system copies; the xcb-build
# copies are nested deep under home/thayne/…/xcb-build/lib/ and are untouched.
find "${_appdir}/lib" -maxdepth 1 -name 'libxcb.so*' -delete 2>/dev/null || true
find "${_appdir}/lib" -maxdepth 1 -name 'libXau.so*' -delete 2>/dev/null || true
find "${_appdir}/lib" -maxdepth 1 -name 'libXdmcp.so*' -delete 2>/dev/null || true
# Qt Multimedia's FFmpeg backend — belt-and-suspenders removal.
#
# citron is built with -DCITRON_USE_BUNDLED_FFMPEG=ON (static FFmpeg for game
# media decoding) and -DCITRON_USE_QT_MULTIMEDIA=OFF (set in build-citron-linux.sh).
# With CITRON_USE_QT_MULTIMEDIA=OFF these files are never staged because Qt
# Multimedia is not linked. These find/delete lines remain as a safety net in
# case the flag is ever changed or if a future Qt version starts staging the
# shared FFmpeg unconditionally.
#
# Background: Qt Multimedia pulls in a SEPARATE shared FFmpeg
# (libavcodec.so.61 ~80 MB, libavformat.so.61, libavutil.so.59, libswresample,
# libswscale) via libffmpegmediaplugin.so. This is distinct from citron's own
# statically compiled FFmpeg and adds ~100 MB to the AppImage for no benefit
# (all camera/video recording code is Qt5-only and permanently dead under Qt6).
find "${_appdir}" -name 'libffmpegmediaplugin.so' -delete 2>/dev/null || true
find "${_appdir}" -name 'libQt6FFmpegStub-*.so*' -delete 2>/dev/null || true
find "${_appdir}" -name 'libavcodec.so*' -delete 2>/dev/null || true
find "${_appdir}" -name 'libavformat.so*' -delete 2>/dev/null || true
find "${_appdir}" -name 'libavutil.so*' -delete 2>/dev/null || true
find "${_appdir}" -name 'libswresample.so*' -delete 2>/dev/null || true
find "${_appdir}" -name 'libswscale.so*' -delete 2>/dev/null || true
# Write a portable qt.conf next to libQt6Core.so.6 in shared/lib/.
#
# CPM Qt's libQt6Core.so.6 has a baked-in INSTALL_PREFIX of
# /home/<user>/cpmcache/qt-bin/6.9.x/gcc_64. Without an explicit qt.conf,
# Qt uses that absolute prefix to locate its plugins and translations at
# runtime — which works on the build machine (the CPM directory exists on
# disk, outside the AppImage) but silently breaks on every other machine.
#
# A qt.conf placed next to libQt6Core.so.6 (AppDir/shared/lib/) overrides
# the embedded prefix. All [Paths] entries are relative to Prefix; Prefix
# itself is relative to the qt.conf file's directory.
#
# qt.conf at: AppDir/shared/lib/qt.conf
# Prefix = .. → AppDir/shared/
# Plugins = lib → AppDir/shared/lib/ (flat dir where lib4bin stages all plugins)
# Translations → AppDir/usr/share/qt6/translations
#
# This also clobbers any qt.conf from the CPM installation that lib4bin or
# strace may have staged here, ensuring absolute build-machine paths cannot
# escape into the AppImage.
mkdir -p ./AppDir/shared/lib
cat > ./AppDir/shared/lib/qt.conf << 'QTCONF_EOF'
[Paths]
Prefix = ..
Plugins = lib
Imports = lib/qt6/qml
Qml2Imports = lib/qt6/qml
Translations = lib/qt6/translations
QTCONF_EOF
# Rename app in desktop file if building a devel/nightly AppImage
if [ "${DEVEL:-false}" = 'true' ]; then
sed -i 's|^Name=citron$|Name=citron nightly|' ./AppDir/*.desktop 2>/dev/null || true
fi
# Allow system Vulkan ICD to override the bundled one at runtime, and write
# PGO profile data next to the running AppImage on exit (matches the old
# linuxdeploy $APPIMAGE_DIR convention). $APPIMAGE is exported by sharun's
# AppRun at runtime and points to the AppImage file's own path.
{
printf 'SHARUN_ALLOW_SYS_VK_ICD=1\n'
printf 'LLVM_PROFILE_FILE=$(dirname "$APPIMAGE")/default-%%p.profraw\n'
} > ./AppDir/.env
# Build the AppImage
./quick-sharun --make-appimage
mkdir -p "${OUTPATH}"
mv -v ./*.AppImage "${OUTPATH}/" 2>/dev/null || true
mv -v ./*.AppImage.* "${OUTPATH}/" 2>/dev/null || true
# Pack the portable tar.zst alongside the AppImage — the "+ tar.zst" half
# promised by this script's header comment, previously unimplemented.
#
# AppDir at this point is the exact same fully-debloated tree that was just
# squashed into the AppImage above (every DEPLOY_*/STRACE_MODE suppression and
# the post-dep-scan find/-delete block above already ran before
# --make-appimage), so this is a complete, AppRun-runnable, install-free
# alternative to the AppImage — not a partial copy. (AppDir/usr/ is not used
# by this layout — translations land under AppDir/lib/qt6/, see comment above —
# so packing only "usr", as the pre-quick-sharun pipeline did, would produce a
# near-empty archive here.)
#
# AppDir is then removed. It has no further purpose once packed, and leaving
# it sitting in OUTPATH means anything that uploads/copies OUTPATH wholesale
# (e.g. a CI artifact upload step) drags along every loose unpacked library a
# second time alongside the AppImage that already contains them compressed.
if [ -d ./AppDir ]; then
if ! command -v zstd >/dev/null 2>&1; then
printf 'ERROR: zstd is not installed — cannot produce %s.tar.zst\n' "${OUTNAME_BASE}" >&2
printf 'Install zstd (e.g. apt-get install zstd) and re-run.\n' >&2
exit 1
fi
tar -c --zstd -f "${OUTPATH}/${OUTNAME_BASE}.tar.zst" -C ./AppDir .
rm -rf ./AppDir
fi
# Clean up build-tool byproducts that land in this same directory (OUTPATH),
# not just AppDir. build-citron-linux.sh sets OUTPATH to the same working
# directory this script cd's into and downloads tooling into, so anything
# these tools write to cwd ends up sitting next to the final artifacts unless
# removed explicitly:
#
# quick-sharun — the tool itself, copied to ./quick-sharun near the top of
# this script (see the vendoring comment above). It has no
# purpose after packaging finishes.
# appinfo — a debug/metadata text file written to cwd by the
# underlying appimagetool binary during --make-appimage.
# Not generated by quick-sharun.sh itself, not meant for
# redistribution — internal build debug output only.
rm -f ./quick-sharun ./appinfo
echo "Artifacts in: ${OUTPATH}"