mirror of
https://github.com/storytold/gridcraft.git
synced 2026-10-25 11:16:49 -06:00
Pin the reviewed fork and shared dependencies, adapt input and rendering to the new frame lifecycle, and preserve bounded capture handling and font notices. Add representative accessibility checks and meaningful control labels. Validation: full repository CI passed all ten stages, with 2,249 tests passed, zero failed and one existing timing test ignored. Native workflows and paired performance qualification remain documented separately.
194 lines
8.6 KiB
Bash
Executable File
194 lines
8.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Build and package GridCraft for Linux (<arch> is x86_64 or aarch64, or riscv64 when cross-compiling):
|
|
#
|
|
# $DIST/gridcraft-<version>-linux-<arch>.AppImage any distro with glibc >= the build host's
|
|
# $DIST/gridcraft-<version>-linux-<arch>.AppImage.zsync delta updates (needs zsyncmake)
|
|
# $DIST/gridcraft-<version>-linux-<arch>.deb Debian, Ubuntu, Mint, Pop!_OS, ...
|
|
# $DIST/gridcraft-<version>-linux-<arch>.rpm Fedora, openSUSE, RHEL, ...
|
|
# $DIST/gridcraft-<version>-linux-<arch>.tar.gz plain FHS-style tree (bin/, share/)
|
|
#
|
|
# Usage: packaging/linux/package.sh [--skip-build] [--formats "appimage deb rpm tar"]
|
|
#
|
|
# Needs: cargo; nfpm for deb/rpm (https://nfpm.goreleaser.com); appimagetool and the AppImage
|
|
# runtime for the AppImage (pinned downloads into $CARGO_TARGET_DIR, or APPIMAGETOOL and
|
|
# APPIMAGE_RUNTIME, e.g. on riscv64, which has no pinned ones). Build on an old distro (CI: Ubuntu 22.04,
|
|
# glibc 2.35) so the binaries run on newer ones. Optional: desktop-file-validate, appstreamcli,
|
|
# zsyncmake (the zsync package) for the AppImage's .zsync.
|
|
set -euo pipefail
|
|
# shellcheck source=../env.sh
|
|
. "$(dirname "${BASH_SOURCE[0]}")/../env.sh"
|
|
HERE="$ROOT/packaging/linux"
|
|
APP_ID=ai.storyteller.gridcraft
|
|
|
|
SKIP_BUILD=0
|
|
FORMATS="appimage deb rpm tar"
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--skip-build) SKIP_BUILD=1; shift ;;
|
|
--formats) FORMATS="$2"; shift 2 ;;
|
|
-h | --help) sed -n '2,18p' "$0"; exit 0 ;;
|
|
*) echo "unknown argument: $1" >&2; exit 2 ;;
|
|
esac
|
|
done
|
|
|
|
ARCH="${CROSS_ARCH:-$(uname -m)}"
|
|
case "$ARCH" in
|
|
riscv64) DEB_ARCH=riscv64 ;;
|
|
x86_64) DEB_ARCH=amd64 ;;
|
|
aarch64 | arm64) ARCH=aarch64; DEB_ARCH=arm64 ;;
|
|
*) echo "unsupported architecture $ARCH" >&2; exit 2 ;;
|
|
esac
|
|
export GRIDCRAFT_MAINTAINER="${GRIDCRAFT_MAINTAINER:-GridCraft maintainers <gridcraft@storyteller.ai>}"
|
|
BASENAME="gridcraft-$VERSION-linux-$ARCH"
|
|
|
|
echo "==> GridCraft $VERSION for Linux $ARCH ($FORMATS)"
|
|
|
|
if [[ "$SKIP_BUILD" = 0 ]]; then
|
|
if [[ -n "${CROSS_TARGET:-}" ]]; then
|
|
(cd "$ROOT" && cargo build --release --locked -p gridcraft -p gridcraft-cli --target "$CROSS_TARGET")
|
|
else
|
|
(cd "$ROOT" && cargo build --release --locked -p gridcraft -p gridcraft-cli)
|
|
fi
|
|
fi
|
|
BIN="$CARGO_TARGET_DIR/${CROSS_TARGET:+$CROSS_TARGET/}release"
|
|
WORK="$CARGO_TARGET_DIR/linux-package"
|
|
STAGE="$WORK/root"
|
|
rm -rf "$WORK"
|
|
|
|
# ---- stage an FHS tree (shared by every format) -------------------------------------------------
|
|
install -Dm755 "$BIN/gridcraft" "$STAGE/usr/bin/gridcraft"
|
|
install -Dm755 "$BIN/gridcraft-cli" "$STAGE/usr/bin/gridcraft-cli"
|
|
"${CROSS_COMPILE:-}strip" "$STAGE/usr/bin/gridcraft" "$STAGE/usr/bin/gridcraft-cli" 2>/dev/null || true
|
|
install -Dm644 "$HERE/$APP_ID.desktop" "$STAGE/usr/share/applications/$APP_ID.desktop"
|
|
install -Dm644 "$HERE/$APP_ID.mime.xml" "$STAGE/usr/share/mime/packages/$APP_ID.xml"
|
|
mkdir -p "$STAGE/usr/share/metainfo"
|
|
sed -e "s/@VERSION@/$VERSION/g" -e "s/@DATE@/$GRIDCRAFT_BUILD_DATE/g" \
|
|
"$HERE/$APP_ID.metainfo.xml.in" >"$STAGE/usr/share/metainfo/$APP_ID.metainfo.xml"
|
|
mkdir -p "$STAGE/usr/share/icons"
|
|
cp -R "$ROOT/assets/app-icon/hicolor" "$STAGE/usr/share/icons/"
|
|
mkdir -p "$STAGE/usr/share/doc/gridcraft"
|
|
copy_docs "$STAGE/usr/share/doc/gridcraft"
|
|
|
|
if command -v desktop-file-validate >/dev/null; then
|
|
desktop-file-validate "$STAGE/usr/share/applications/$APP_ID.desktop"
|
|
fi
|
|
if command -v appstreamcli >/dev/null; then
|
|
appstreamcli validate --no-net --explain "$STAGE/usr/share/metainfo/$APP_ID.metainfo.xml"
|
|
fi
|
|
|
|
has() {
|
|
local format="$1"
|
|
case " $FORMATS " in *" $format "*) return 0 ;; *) return 1 ;; esac
|
|
}
|
|
|
|
# ---- .tar.gz ------------------------------------------------------------------------------------
|
|
if has tar; then
|
|
mkdir -p "$WORK/tar"
|
|
cp -R "$STAGE/usr" "$WORK/tar/$BASENAME"
|
|
tar -C "$WORK/tar" -czf "$DIST/$BASENAME.tar.gz" "$BASENAME"
|
|
echo "wrote $DIST/$BASENAME.tar.gz"
|
|
fi
|
|
|
|
# ---- .deb / .rpm --------------------------------------------------------------------------------
|
|
if has deb || has rpm; then
|
|
command -v nfpm >/dev/null || { echo "error: nfpm not found (https://nfpm.goreleaser.com/install/)" >&2; exit 1; }
|
|
export VERSION
|
|
export NFPM_ARCH="$DEB_ARCH"
|
|
# nfpm expands env vars in fields like `version` and `arch`, but not in `contents[].src`.
|
|
sed "s|\${STAGE}|$STAGE|g" "$HERE/nfpm.yaml" >"$WORK/nfpm.yaml"
|
|
for fmt in deb rpm; do
|
|
if has "$fmt"; then (cd "$ROOT" && nfpm package -f "$WORK/nfpm.yaml" -p "$fmt" -t "$DIST/$BASENAME.$fmt"); fi
|
|
done
|
|
fi
|
|
|
|
# ---- AppImage -----------------------------------------------------------------------------------
|
|
if has appimage; then
|
|
APPIMAGETOOL_VERSION=1.9.1
|
|
# appimagetool would otherwise embed type2-runtime's unchecked `continuous` build.
|
|
APPIMAGE_RUNTIME_VERSION=20251108
|
|
case "$ARCH" in
|
|
x86_64)
|
|
APPIMAGETOOL_SHA256=ed4ce84f0d9caff66f50bcca6ff6f35aae54ce8135408b3fa33abfc3cb384eb0
|
|
APPIMAGE_RUNTIME_SHA256=2fca8b443c92510f1483a883f60061ad09b46b978b2631c807cd873a47ec260d
|
|
;;
|
|
aarch64)
|
|
APPIMAGETOOL_SHA256=f0837e7448a0c1e4e650a93bb3e85802546e60654ef287576f46c71c126a9158
|
|
APPIMAGE_RUNTIME_SHA256=00cbdfcf917cc6c0ff6d3347d59e0ca1f7f45a6df1a428a0d6d8a78664d87444
|
|
;;
|
|
*) APPIMAGETOOL_SHA256='' APPIMAGE_RUNTIME_SHA256='' ;;
|
|
esac
|
|
# Downloads a pinned file into the cache once and checks its hash on every run.
|
|
fetch_pinned() {
|
|
local path="$1" url="$2" sha256="$3"
|
|
if [[ ! -x "$path" ]]; then
|
|
curl --proto '=https' -fsSL -o "$path.download" "$url"
|
|
printf '%s %s\n' "$sha256" "$path.download" | sha256sum -c -
|
|
chmod 755 "$path.download"
|
|
mv "$path.download" "$path"
|
|
fi
|
|
printf '%s %s\n' "$sha256" "$path" | sha256sum -c -
|
|
return
|
|
}
|
|
APPDIR="$WORK/GridCraft.AppDir"
|
|
cp -R "$STAGE" "$APPDIR"
|
|
ln -s usr/bin/gridcraft "$APPDIR/AppRun"
|
|
cp "$HERE/$APP_ID.desktop" "$APPDIR/$APP_ID.desktop"
|
|
cp "$ROOT/assets/app-icon/hicolor/256x256/apps/$APP_ID.png" "$APPDIR/$APP_ID.png"
|
|
ln -s "$APP_ID.png" "$APPDIR/.DirIcon"
|
|
|
|
TOOL="${APPIMAGETOOL:-$(command -v appimagetool || true)}"
|
|
if [[ -z "$TOOL" ]] && [[ -z "$APPIMAGETOOL_SHA256" ]]; then
|
|
echo "no pinned appimagetool for $ARCH; set APPIMAGETOOL or put appimagetool on PATH" >&2
|
|
exit 2
|
|
fi
|
|
RUNTIME="${APPIMAGE_RUNTIME:-}"
|
|
if [[ -z "$RUNTIME" ]] && [[ -z "$APPIMAGE_RUNTIME_SHA256" ]]; then
|
|
echo "no pinned AppImage runtime for $ARCH; set APPIMAGE_RUNTIME" >&2
|
|
exit 2
|
|
fi
|
|
if [[ -z "$TOOL" ]]; then
|
|
TOOL="$CARGO_TARGET_DIR/appimagetool-$APPIMAGETOOL_VERSION-$ARCH.AppImage"
|
|
fetch_pinned "$TOOL" \
|
|
"https://github.com/AppImage/appimagetool/releases/download/$APPIMAGETOOL_VERSION/appimagetool-$ARCH.AppImage" \
|
|
"$APPIMAGETOOL_SHA256"
|
|
fi
|
|
if [[ -z "$RUNTIME" ]]; then
|
|
RUNTIME="$CARGO_TARGET_DIR/appimage-runtime-$APPIMAGE_RUNTIME_VERSION-$ARCH"
|
|
fetch_pinned "$RUNTIME" \
|
|
"https://github.com/AppImage/type2-runtime/releases/download/$APPIMAGE_RUNTIME_VERSION/runtime-$ARCH" \
|
|
"$APPIMAGE_RUNTIME_SHA256"
|
|
fi
|
|
# Absolute, because appimagetool runs in $DIST below (CARGO_TARGET_DIR, APPIMAGETOOL or
|
|
# APPIMAGE_RUNTIME may be relative, e.g. target/agent-<name>).
|
|
OUT="$(cd "$DIST" && pwd)/$BASENAME.AppImage"
|
|
APPDIR="$(cd "$APPDIR" && pwd)"
|
|
TOOL="$(cd "$(dirname "$TOOL")" && pwd)/$(basename "$TOOL")"
|
|
RUNTIME="$(cd "$(dirname "$RUNTIME")" && pwd)/$(basename "$RUNTIME")"
|
|
# A .zsync left by an earlier run would hide a missing zsyncmake and describe another file.
|
|
rm -f "$OUT.zsync"
|
|
# Update information: AppImageUpdate, AppImageLauncher and the like read it from the file and
|
|
# fetch only the blocks that changed in a newer release, through the .zsync published next to
|
|
# each AppImage on GitHub Releases. `latest` is the newest published release that is not a
|
|
# pre-release. A fork's builds point at its own releases through GITHUB_REPOSITORY.
|
|
REPO="${GITHUB_REPOSITORY:-storytold/gridcraft}"
|
|
UPDATE_INFO="gh-releases-zsync|${REPO%%/*}|${REPO#*/}|latest|gridcraft-*-linux-$ARCH.AppImage.zsync"
|
|
# Extract-and-run: works without FUSE (containers, CI). The output embeds the static runtime,
|
|
# so users don't need libfuse2 either. With zsyncmake on the host (CI installs the zsync
|
|
# package) appimagetool also writes the .zsync, into its working directory, hence the cd.
|
|
(cd "$DIST" && ARCH="$ARCH" APPIMAGE_EXTRACT_AND_RUN=1 "$TOOL" --no-appstream --runtime-file "$RUNTIME" -u "$UPDATE_INFO" "$APPDIR" "$OUT")
|
|
echo "wrote $OUT"
|
|
if [[ -s "$OUT.zsync" ]]; then
|
|
echo "wrote $OUT.zsync"
|
|
else
|
|
warn "zsyncmake not found, so $OUT.zsync was not written; AppImage delta updates need it"
|
|
fi
|
|
fi
|
|
|
|
if [[ -z "${CROSS_TARGET:-}" ]]; then
|
|
"$STAGE/usr/bin/gridcraft-cli" --version
|
|
elif [[ -n "${EMULATOR:-}" ]]; then
|
|
"$EMULATOR" "$STAGE/usr/bin/gridcraft-cli" --version
|
|
fi
|
|
echo "==> done"
|
|
ls -lh "$DIST"
|