182 lines
6.7 KiB
Bash
182 lines
6.7 KiB
Bash
|
|
#!/usr/bin/env bash
|
||
|
|
# build-appimage.sh — package a php-qml app as a single-file Linux
|
||
|
|
# AppImage using linuxdeploy + linuxdeploy-plugin-qt.
|
||
|
|
#
|
||
|
|
# Usage:
|
||
|
|
# build-appimage.sh \
|
||
|
|
# --app-name todo \
|
||
|
|
# --host-binary build/qml/todo \
|
||
|
|
# --symfony-dir symfony \
|
||
|
|
# --frankenphp /usr/local/bin/frankenphp \
|
||
|
|
# --caddyfile Caddyfile \
|
||
|
|
# --desktop packaging/todo.desktop \
|
||
|
|
# --icon packaging/todo.png \
|
||
|
|
# --output build/Todo-x86_64.AppImage
|
||
|
|
#
|
||
|
|
# Layout produced inside the AppImage:
|
||
|
|
# AppDir/usr/bin/<app-name> — Qt host
|
||
|
|
# AppDir/usr/bin/frankenphp — bundled FrankenPHP
|
||
|
|
# AppDir/usr/share/<app-name>/symfony/ — Composer-installed app
|
||
|
|
# AppDir/usr/share/<app-name>/Caddyfile — bundled Caddy config
|
||
|
|
# AppDir/usr/lib/... — Qt + system deps (linuxdeploy)
|
||
|
|
#
|
||
|
|
# BackendConnection's resolve* methods already look in
|
||
|
|
# applicationDirPath()/../share/<app>/... so this layout works.
|
||
|
|
|
||
|
|
set -euo pipefail
|
||
|
|
|
||
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||
|
|
TOOLS_DIR="$SCRIPT_DIR/tools"
|
||
|
|
|
||
|
|
APP_NAME=""
|
||
|
|
HOST_BIN=""
|
||
|
|
SYMFONY_DIR=""
|
||
|
|
FRANKENPHP=""
|
||
|
|
CADDYFILE=""
|
||
|
|
DESKTOP=""
|
||
|
|
ICON=""
|
||
|
|
OUTPUT=""
|
||
|
|
|
||
|
|
usage() {
|
||
|
|
cat <<USAGE >&2
|
||
|
|
Usage: $0 \\
|
||
|
|
--app-name <name> \\
|
||
|
|
--host-binary <path> \\
|
||
|
|
--symfony-dir <path> \\
|
||
|
|
--frankenphp <path> \\
|
||
|
|
--caddyfile <path> \\
|
||
|
|
--desktop <path> \\
|
||
|
|
--icon <path> \\
|
||
|
|
--output <path.AppImage>
|
||
|
|
USAGE
|
||
|
|
exit 1
|
||
|
|
}
|
||
|
|
|
||
|
|
while [ $# -gt 0 ]; do
|
||
|
|
case "$1" in
|
||
|
|
--app-name) APP_NAME="$2"; shift 2 ;;
|
||
|
|
--host-binary) HOST_BIN="$2"; shift 2 ;;
|
||
|
|
--symfony-dir) SYMFONY_DIR="$2"; shift 2 ;;
|
||
|
|
--frankenphp) FRANKENPHP="$2"; shift 2 ;;
|
||
|
|
--caddyfile) CADDYFILE="$2"; shift 2 ;;
|
||
|
|
--desktop) DESKTOP="$2"; shift 2 ;;
|
||
|
|
--icon) ICON="$2"; shift 2 ;;
|
||
|
|
--output) OUTPUT="$2"; shift 2 ;;
|
||
|
|
-h|--help) usage ;;
|
||
|
|
*) echo "unknown arg: $1" >&2; usage ;;
|
||
|
|
esac
|
||
|
|
done
|
||
|
|
|
||
|
|
for v in APP_NAME HOST_BIN SYMFONY_DIR FRANKENPHP CADDYFILE DESKTOP ICON OUTPUT; do
|
||
|
|
[ -n "${!v}" ] || { echo "missing --${v,,}" >&2; usage; }
|
||
|
|
done
|
||
|
|
|
||
|
|
# Resolve to absolute paths so a `cd` later doesn't break references.
|
||
|
|
HOST_BIN="$(readlink -f "$HOST_BIN")"
|
||
|
|
SYMFONY_DIR="$(readlink -f "$SYMFONY_DIR")"
|
||
|
|
FRANKENPHP="$(readlink -f "$FRANKENPHP")"
|
||
|
|
CADDYFILE="$(readlink -f "$CADDYFILE")"
|
||
|
|
DESKTOP="$(readlink -f "$DESKTOP")"
|
||
|
|
ICON="$(readlink -f "$ICON")"
|
||
|
|
OUTPUT_DIR="$(dirname "$OUTPUT")"
|
||
|
|
mkdir -p "$OUTPUT_DIR"
|
||
|
|
OUTPUT="$(readlink -f "$OUTPUT_DIR")/$(basename "$OUTPUT")"
|
||
|
|
|
||
|
|
[ -x "$HOST_BIN" ] || { echo "host binary not executable: $HOST_BIN" >&2; exit 1; }
|
||
|
|
[ -d "$SYMFONY_DIR" ] || { echo "symfony dir missing: $SYMFONY_DIR" >&2; exit 1; }
|
||
|
|
[ -x "$FRANKENPHP" ] || { echo "frankenphp not executable: $FRANKENPHP" >&2; exit 1; }
|
||
|
|
[ -f "$CADDYFILE" ] || { echo "caddyfile missing: $CADDYFILE" >&2; exit 1; }
|
||
|
|
[ -f "$DESKTOP" ] || { echo "desktop file missing: $DESKTOP" >&2; exit 1; }
|
||
|
|
[ -f "$ICON" ] || { echo "icon missing: $ICON" >&2; exit 1; }
|
||
|
|
|
||
|
|
mkdir -p "$TOOLS_DIR"
|
||
|
|
|
||
|
|
step() { echo "==> $*"; }
|
||
|
|
|
||
|
|
# ── Tooling ───────────────────────────────────────────────────────────
|
||
|
|
LD="$TOOLS_DIR/linuxdeploy-x86_64.AppImage"
|
||
|
|
LD_QT="$TOOLS_DIR/linuxdeploy-plugin-qt-x86_64.AppImage"
|
||
|
|
APPIMAGETOOL="$TOOLS_DIR/appimagetool-x86_64.AppImage"
|
||
|
|
|
||
|
|
if [ ! -x "$LD" ]; then
|
||
|
|
step "Download linuxdeploy"
|
||
|
|
curl -fsSL -o "$LD" \
|
||
|
|
https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage
|
||
|
|
chmod +x "$LD"
|
||
|
|
fi
|
||
|
|
if [ ! -x "$LD_QT" ]; then
|
||
|
|
step "Download linuxdeploy-plugin-qt"
|
||
|
|
curl -fsSL -o "$LD_QT" \
|
||
|
|
https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage
|
||
|
|
chmod +x "$LD_QT"
|
||
|
|
fi
|
||
|
|
if [ ! -x "$APPIMAGETOOL" ]; then
|
||
|
|
step "Download appimagetool"
|
||
|
|
curl -fsSL -o "$APPIMAGETOOL" \
|
||
|
|
https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage
|
||
|
|
chmod +x "$APPIMAGETOOL"
|
||
|
|
fi
|
||
|
|
# Pre-cache the runtime file appimagetool needs; downloading it on every
|
||
|
|
# build was wedging some networks during squashfs.
|
||
|
|
RUNTIME_FILE="$TOOLS_DIR/runtime-x86_64"
|
||
|
|
if [ ! -f "$RUNTIME_FILE" ]; then
|
||
|
|
step "Download AppImage runtime"
|
||
|
|
curl -fsSL -o "$RUNTIME_FILE" \
|
||
|
|
https://github.com/AppImage/type2-runtime/releases/download/continuous/runtime-x86_64
|
||
|
|
fi
|
||
|
|
|
||
|
|
# Some sandboxed CI images can't run AppImage's FUSE mount. Allow the
|
||
|
|
# user to pre-extract them and skip the FUSE machinery via env hint.
|
||
|
|
export APPIMAGE_EXTRACT_AND_RUN="${APPIMAGE_EXTRACT_AND_RUN:-1}"
|
||
|
|
|
||
|
|
# ── Stage AppDir ──────────────────────────────────────────────────────
|
||
|
|
APPDIR="$(mktemp -d)/AppDir"
|
||
|
|
trap 'rm -rf "$(dirname "$APPDIR")"' EXIT INT TERM
|
||
|
|
|
||
|
|
step "Stage AppDir at $APPDIR"
|
||
|
|
mkdir -p "$APPDIR/usr/bin" "$APPDIR/usr/share/$APP_NAME"
|
||
|
|
install -m 0755 "$HOST_BIN" "$APPDIR/usr/bin/$APP_NAME"
|
||
|
|
install -m 0755 "$FRANKENPHP" "$APPDIR/usr/bin/frankenphp"
|
||
|
|
install -m 0644 "$CADDYFILE" "$APPDIR/usr/share/$APP_NAME/Caddyfile"
|
||
|
|
|
||
|
|
# Symfony tree: rsync without dev artefacts. Ensure composer ran with
|
||
|
|
# --no-dev before building; this script doesn't re-run composer install.
|
||
|
|
rsync -a --delete \
|
||
|
|
--exclude='var/cache' \
|
||
|
|
--exclude='var/log' \
|
||
|
|
--exclude='var/data.sqlite*' \
|
||
|
|
--exclude='vendor/symfony/maker-bundle' \
|
||
|
|
"$SYMFONY_DIR/" "$APPDIR/usr/share/$APP_NAME/symfony/"
|
||
|
|
|
||
|
|
# ── Run linuxdeploy + Qt plugin ───────────────────────────────────────
|
||
|
|
# linuxdeploy refuses to run as root; not an issue under normal users.
|
||
|
|
step "linuxdeploy --plugin qt --output appimage"
|
||
|
|
cd "$(dirname "$APPDIR")"
|
||
|
|
|
||
|
|
# linuxdeploy reads QML_SOURCES_PATHS to find QML modules to bundle.
|
||
|
|
# Point it at the example's QML so its plugin gets picked up.
|
||
|
|
QML_SOURCES_PATHS=""
|
||
|
|
for d in "$SYMFONY_DIR/../qml" "$SYMFONY_DIR/../../qml"; do
|
||
|
|
[ -d "$d" ] && QML_SOURCES_PATHS="$d"
|
||
|
|
done
|
||
|
|
|
||
|
|
# Stage the AppDir + Qt deps first; then call appimagetool ourselves
|
||
|
|
# so the slow squashfs step is observable (linuxdeploy's bundled
|
||
|
|
# appimagetool download path was opaque on first run).
|
||
|
|
QML_SOURCES_PATHS="$QML_SOURCES_PATHS" \
|
||
|
|
EXTRA_PLATFORM_PLUGINS=libqoffscreen.so \
|
||
|
|
LD_LIBRARY_PATH="" \
|
||
|
|
"$LD" \
|
||
|
|
--appdir "$APPDIR" \
|
||
|
|
--executable "$APPDIR/usr/bin/$APP_NAME" \
|
||
|
|
--desktop-file "$DESKTOP" \
|
||
|
|
--icon-file "$ICON" \
|
||
|
|
--plugin qt
|
||
|
|
|
||
|
|
step "appimagetool — squash AppDir into single-file AppImage"
|
||
|
|
"$APPIMAGETOOL" --runtime-file "$RUNTIME_FILE" "$APPDIR" "$OUTPUT"
|
||
|
|
|
||
|
|
echo
|
||
|
|
echo "✓ AppImage at $OUTPUT"
|
||
|
|
ls -la "$OUTPUT"
|