feat: Added Torzu's AppImageBuilder

Added support for building AppImages using Torzu's AppImageBuilder.

Signed-off-by: Boss.sfc <boss.sfc@citron-emu.org>
This commit is contained in:
Boss.sfc
2025-07-31 14:40:22 +07:00
parent c5dce33345
commit 317a8b2bd5
14 changed files with 426 additions and 0 deletions

83
AppImage-build-debian-inner.sh Executable file
View File

@@ -0,0 +1,83 @@
#! /bin/bash
set -e
# Make sure script is called from inside our container
test -e /tmp/torzu-src-ro || (echo "Script MUST NOT be called directly!" ; exit 1)
# Set up environment
export LANG=C.UTF-8
export LC_ALL=C.UTF-8
unset LC_ADDRESS LC_NAME LC_MONETARY LC_PAPER LC_TELEPHONE LC_MEASUREMENT LC_TIME
# Raise max open files count
ulimit -n 50000
# Install dependencies
apt -y install cmake ninja-build build-essential autoconf pkg-config locales wget git file mold libtool lsb-release wget software-properties-common gnupg \
qtbase5-dev qtmultimedia5-dev qtbase5-private-dev glslang-tools libssl-dev libavcodec-dev libavfilter-dev libavutil-dev libswscale-dev libpulse-dev libasound2-dev
if [ ! "$BUILD_USE_CPM" = 1 ]; then
apt -y install libfmt-dev libenet-dev liblz4-dev nlohmann-json3-dev zlib1g-dev libopus-dev libsimpleini-dev libstb-dev libzstd-dev libusb-1.0-0-dev libcubeb-dev libcpp-jwt-dev libvulkan-dev gamemode-dev libasound2-dev libglu1-mesa-dev libxext-dev mesa-common-dev libva-dev
if [ ! -f /usr/local/lib/cmake/Boost-1.88.0/BoostConfigVersion.cmake ]; then
# Install Boost
wget https://archives.boost.io/release/1.88.0/source/boost_1_88_0.tar.bz2
echo "Extracting Boost sources..."
tar xf boost_1_88_0.tar.bz2
cd boost_1_88_0
./bootstrap.sh
./b2 install --with-{headers,context,system,fiber,atomic,filesystem} link=static
cd ..
rm -rf boost_1_88_0 boost_1_88_0.tar.bz2
fi
fi
# Install Clang
if ([ "$BUILD_USE_CLANG" = 1 ] && ! clang-19 --version); then
cd /tmp
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
./llvm.sh 19
rm llvm.sh
fi
# Mount Torzu sources with temporary overlay
cd /tmp
mkdir torzu-src-upper torzu-src-work torzu-src
mount -t overlay overlay -olowerdir=torzu-src-ro,upperdir=torzu-src-upper,workdir=torzu-src-work torzu-src
# Get extra configuration/compilation options
EXTRA_COMPILE_FLAGS=""
EXTRA_CMAKE_FLAGS=""
if [ "$BUILD_USE_CLANG" = 1 ]; then
EXTRA_CMAKE_FLAGS="-DCMAKE_C_COMPILER=clang-19 -DCMAKE_CXX_COMPILER=clang++-19"
FATLTO_FLAG="-flto=full"
else
FATLTO_FLAG="-flto"
fi
if [ "$BUILD_USE_THIN_LTO" = 1 ]; then
EXTRA_COMPILE_FLAGS="-flto=thin"
fi
if [ "$BUILD_USE_FAT_LTO" = 1 ]; then
EXTRA_COMPILE_FLAGS="$FATLTO_FLAG"
fi
if [ "$BUILD_USE_CPM" = 1 ]; then
EXTRA_CMAKE_FLAGS="$EXTRA_CMAKE_FLAGS -DYUZU_USE_CPM=ON"
fi
# Build Torzu
cd /tmp
mkdir torzu-build
cd torzu-build
cmake /tmp/torzu-src -GNinja -DCMAKE_BUILD_TYPE=Release -DYUZU_TESTS=OFF -DENABLE_QT_TRANSLATION=OFF -DSPIRV_WERROR=OFF -DCMAKE_FIND_LIBRARY_SUFFIXES=".a;.so" -DSPIRV-Headers_SOURCE_DIR=/tmp/torzu-src/externals/SPIRV-Headers -DCMAKE_{C,CXX}_FLAGS="$EXTRA_COMPILE_FLAGS -fdata-sections -ffunction-sections" -DCMAKE_{EXE,SHARED}_LINKER_FLAGS="-Wl,--gc-sections" $EXTRA_CMAKE_FLAGS
ninja || (
echo "Compilation has failed. Dropping you into a shell so you can inspect the situation. Run 'ninja' to retry and exit shell once compilation has finished successfully."
echo "Note that any changes made here will not be reflected to the host environment, but changes made from the host environment will be reflected here."
bash
)
# Generate AppImage
cp -rv /tmp/torzu-src/AppImageBuilder /tmp/AppImageBuilder
cd /tmp/AppImageBuilder
./build.sh /tmp/torzu-build /tmp/torzu.AppImage || echo "This error is known. Using workaround..."
cp /lib/$(uname -m)-linux-gnu/libICE.so.6 build/
mv build /tmp/hosttmp/torzu-debian-appimage-rootfs

73
AppImage-build-debian.sh Executable file
View File

@@ -0,0 +1,73 @@
#! /bin/bash
set -e
# Parse options
for i in "$@"
do
case $i in
-l|--clang)
export BUILD_USE_CLANG=1
echo "-> Using Clang for compilation."
;;
-o|--thin-lto)
export BUILD_USE_THIN_LTO=1
echo "-> Thin link time optimization enabled."
;;
-O|--fat-lto)
export BUILD_USE_FAT_LTO=1
echo "-> Fat link time optimization enabled."
;;
-p|--use-cpm)
export BUILD_USE_CPM=1
echo "-> Using CPM to download most dependencies."
;;
-k|--keep-rootfs)
BUILD_KEEP_ROOTFS=1
echo "-> Not deleting rootfs after successful build."
;;
*)
echo "Usage: $0 [--clang/-l] [--thin-lto/-o] [--fat-lto/-O] [--use-cpm/-p] [--keep-rootfs/-k]"
exit 1
;;
esac
done
# Make sure options are valid
if [ "$BUILD_USE_THIN_LTO" = 1 ] && [ "$BUILD_USE_CLANG" != 1 ]; then
echo "Thin LTO can't be used without Clang!"
exit 2
fi
if [ "$BUILD_USE_THIN_LTO" = 1 ] && [ "$BUILD_USE_FAT_LTO" = 1 ]; then
echo "Only either thin or fat LTO can be used!"
exit 2
fi
# Get citron source dir
citron_SOURCE_DIR="$(realpath "$(dirname "${BASH_SOURCE[0]}")")"
echo "-> Source dir is $citron_SOURCE_DIR"
rm -rf "$citron_SOURCE_DIR/AppImageBuilder/build"
# Generate debian rootfs
cd /tmp
echo "Cleaning up before build..."
rm -rf citron-debian-appimage-rootfs
[ -d rootfs-citron-appimage-build ] ||
debootstrap stable rootfs-citron-appimage-build http://deb.debian.org/debian/
bwrap --bind rootfs-citron-appimage-build / \
--unshare-pid \
--dev-bind /dev /dev --proc /proc --tmpfs /tmp --ro-bind /sys /sys --dev-bind /run /run \
--tmpfs /var/tmp \
--chmod 1777 /tmp \
--ro-bind /etc/resolv.conf /etc/resolv.conf \
--ro-bind "$citron_SOURCE_DIR" /tmp/citron-src-ro \
--chdir / \
--tmpfs /home \
--setenv HOME /home \
--bind /tmp /tmp/hosttmp \
/tmp/citron-src-ro/AppImage-build-debian-inner.sh
appimagetool citron-debian-appimage-rootfs citron.AppImage
echo "AppImage generated at /tmp/citron.AppImage! Cleaning up..."
rm -rf citron-debian-appimage-rootfs
if [ ! "$BUILD_KEEP_ROOTFS" = 1 ]; then
rm -rf rootfs-citron-appimage-build
fi

42
AppImage-build-local.sh Executable file
View File

@@ -0,0 +1,42 @@
#!/bin/bash
FILE=build/bin/citron
if test -f "$FILE"; then
# remove any previously made AppImage in the base citron git folder
rm ./citron.AppImage
# enter AppImage utility folder
cd AppImageBuilder
# run the build script to create the AppImage
# (usage) ./build.sh [source citron build folder] [destination .AppImage file]
./build.sh ../build ./citron.AppImage
FILE=./citron.AppImage
if test -f "$FILE"; then
# move the AppImage to the main citron folder
mv citron.AppImage ..
# return to main citron folder
cd ..
# show contents of current folder
echo
ls
# show AppImages specifically
echo
ls *.AppImage
echo
echo "'citron.AppImage' is now located in the current folder."
echo
else
cd ..
echo "AppImage was not built."
fi
else
echo
echo "$FILE does not exist."
echo
echo "No citron executable found in the /citron/build/bin folder!"
echo
echo "You must first build a native linux version of citron before running this script!"
echo
fi

11
AppImageBuilder/assets/AppRun Executable file
View File

@@ -0,0 +1,11 @@
#! /bin/bash
cd "$APPDIR"
if [ -d /usr/lib/$(uname -m)-linux-gnu/qt5 ] || [ -d /usr/lib/qt ]; then
# System-wide Qt5
exec ./citron.sh "$@"
else
# Bundled Qt5
exec ./citron-bqt.sh "$@"
fi

View File

@@ -0,0 +1,3 @@
#! /bin/sh
LD_LIBRARY_PATH=./qt5:/usr/lib/$(uname -m)-linux-gnu:/usr/lib:. QT_PLUGIN_PATH=./qt5 exec ./citron "$@"

View File

@@ -0,0 +1,6 @@
[Desktop Entry]
Type=Application
Name=citron
Icon=citron
Exec=AppRun
Categories=Game;

View File

@@ -0,0 +1,3 @@
#! /bin/sh
LD_LIBRARY_PATH=/usr/lib/$(uname -m)-linux-gnu:/usr/lib:. exec ./citron "$@"

View File

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 576.4 576">
<!-- Generator: Adobe Illustrator 29.0.1, SVG Export Plug-In . SVG Version: 2.1.0 Build 192) -->
<defs>
<style>
.st0 {
fill: #2eb34a;
}
.st1 {
fill: #f1b941;
}
.st2 {
fill: #ee852f;
}
.st3 {
fill: #f9ee47;
}
</style>
</defs>
<path class="st2" d="M276.8,40.4c-127.9,4.8-214.7,131.7-172,253.6,24.9,71,96.4,123.6,172,124.6v30.9c-74.2-2-144.5-44-181.9-107.8C9.9,196.9,110.8,15.3,276.8,9.5v30.9Z"/>
<path class="st1" d="M111.7,238.2h151.3l-105.7,108.3c-27.6-28.1-45.1-68.7-45.6-108.3Z"/>
<path class="st1" d="M111.7,221c.4-39.9,18.1-79.7,45.6-108.3l105.7,108.3H111.7Z"/>
<path class="st1" d="M276.8,401.5c-39.5-1.3-76.9-16.5-106.6-42.1l106.6-107.5v149.6Z"/>
<path class="st1" d="M276.8,207.2l-106.6-108.3c10.9-6.4,20.3-14.9,31.5-21,22.8-12.3,49.1-19.5,75.1-20.3v149.6Z"/>
<path class="st0" d="M299.6,535.6c127.9-4.8,214.7-131.7,172-253.6-24.9-71-96.4-123.6-172-124.6v-30.9c74.2,2,144.5,44,181.9,107.8,85,144.9-15.9,326.5-181.9,332.4v-30.9Z"/>
<path class="st3" d="M464.7,337.8h-151.3s105.7-108.3,105.7-108.3c27.6,28.1,45.1,68.7,45.6,108.3Z"/>
<path class="st3" d="M464.7,355c-.4,39.9-18.1,79.7-45.6,108.3l-105.7-108.3h151.3Z"/>
<path class="st3" d="M299.6,174.5c39.5,1.3,76.9,16.5,106.6,42.1l-106.6,107.5v-149.6Z"/>
<path class="st3" d="M299.6,368.8l106.6,108.3c-10.9,6.4-20.3,14.9-31.5,21-22.8,12.3-49.1,19.5-75.1,20.3v-149.6Z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

Binary file not shown.

View File

@@ -0,0 +1 @@
This is a statically compiled bubblewrap 0.9.0 executable.

Binary file not shown.

View File

@@ -0,0 +1 @@
This is a statically compiled bubblewrap 0.9.0 executable.

170
AppImageBuilder/build.sh Executable file
View File

@@ -0,0 +1,170 @@
#! /bin/bash
set -e
# Check arguments
if [[ $# != 2 ]]; then
>&2 echo "Bad usage!"
echo "Usage: $0 <build dir> <output file>"
exit 1
fi
# Get paths
ARCH="$(uname -m)"
SYSTEM_LIBS="/usr/lib"
SYSTEM_LIBS64="/usr/lib64"
citron_BIN="${1}/bin"
citron_BIN_GUI="${citron_BIN}/citron"
# Make sure executable exists
if [[ $(file -b --mime-type "$citron_BIN_GUI") != application/x-pie-executable ]]; then
>&2 echo "Invalid citron executable!"
fi
# Clean up build dir
rm -rf build
mkdir build
# NOTE: some of these aren't used now, but can be reordered in priority when citron is converted to QT6
# QT5 - /usr/lib/${ARCH}-linux-gnu/qt5 (debian), /usr/lib64/qt5 (fedora), /usr/lib/qt (steam deck)
# QT5 - /usr/lib/${ARCH}-linux-gnu/qt6 (debian), /usr/lib64/qt6 (fedora), /usr/lib/qt6 (steam deck)
QTFOUND="true"
QTDIR="$SYSTEM_LIBS"/${ARCH}-linux-gnu/qt5/plugins
if [ ! -d "$QTDIR" ]; then
# default qt5 folder not found, check for 64-bit qt5 folder
QTDIR="$SYSTEM_LIBS64"/qt5/plugins
if [ ! -d "$QTDIR" ]; then
# 64-bit qt5 folder not found, check for Steam Deck qt (qt5) folder
QTDIR="$SYSTEM_LIBS"/qt/plugins
if [ ! -d "$QTDIR" ]; then
# Steam Deck qt (qt5) folder not found, check for regular qt6 folder
QTDIR="$SYSTEM_LIBS"/${ARCH}-linux-gnu/qt6/plugins
if [ ! -d "$QTDIR" ]; then
# regular qt6 folder not found, check for 64-bit qt6 folder
QTDIR="$SYSTEM_LIBS64"/qt6/plugins
if [ ! -d "$QTDIR" ]; then
# 64-bit qt6 folder not found, check for Steam Deck qt6 folder
QTDIR="$SYSTEM_LIBS"/qt6/plugins
if [ ! -d "$QTDIR" ]; then
QTFOUND="false"
fi
fi
fi
fi
fi
fi
if [ $QTFOUND == "true" ]; then
echo "QT plugins from $QTDIR will be used."
# Copy system dependencies used to build and required by the citron binary
# includes:
# - '/lib64/ld-linux-x86-64.so.2' or `/lib/ld-linux-aarch64.so.1` file per architecture
# - required files from `/usr/lib/x86_64-linux-gnu` or `/usr/lib/aarch64-linux-gnu`
# - different for SteamDeck, but still does it automatically
function copy_libs {
for lib in $(ldd "$1"); do
(cp -vn "$lib" ./build/ 2> /dev/null) || true
done
}
echo "Copying main dependencies..."
copy_libs "$citron_BIN_GUI"
# Copy QT dependency folders, path determined above
echo "Copying Qt dependencies..."
mkdir ./build/qt5
cp -rv "$QTDIR"/{imageformats,platforms,platformthemes,xcbglintegrations} ./build/qt5/
# Discover indirect dependencies (mostly from runtime-loaded Qt plugins)
echo "Copying extra dependencies..."
while true; do
LIBS="$(find ./build -name \*.so\*)"
LIB_COUNT=$(echo "$LIBS" | wc -l)
echo "$LIB_COUNT dependency libraries discovered so far..."
if [ $LIB_COUNT == "$PREV_LIB_COUNT" ]; then
break
fi
PREV_LIB_COUNT=$LIB_COUNT
for plib in $LIBS; do
if [ -f "$plib" ]; then
copy_libs "$plib"
fi
done
done
# Copy executable
cp -v "$citron_BIN_GUI" ./build/
# Copy assets for the appropriate arch
cp -v ./assets_"${ARCH}"/* ./build/
# Copy common assets
cp -v ./assets/* ./build/
# Strip all libraries and executables
for file in $(find ./build -type f); do
(strip -v "$file" 2> /dev/null) || true
done
PASSED_CHECKSUM="false"
FILE=appimagetool.AppImage
# total number of times to try downloading if a checksum doesn't match
DL_TRIES=3
while [ $PASSED_CHECKSUM == "false" ] && [ "$DL_TRIES" -gt 0 ]; do
case $ARCH in
x86_64)
# Static copy from the 'ext-linux-bin' repo.
# Checksum will need to be changed when/if this file in the repo is updated.
if ! test -f "$FILE"; then
echo "Downloading appimagetool for architecture '$ARCH'"
wget -O appimagetool.AppImage https://github.com/litucks/ext-linux-bin/raw/refs/heads/main/appimage/appimagetool-x86_64.AppImage
fi
if [ $(shasum -a 256 appimagetool.AppImage | cut -d' ' -f1) = "110751478abece165a18460acbd7fd1398701f74a9405ad8ac053427d937bd5d" ] ; then
PASSED_CHECKSUM="true"
fi
# DISABLED TO USE THE ABOVE
# The current continuous release channel option, until a static copy is put in 'ext-linux-bin'.
# The checksum will pass until the continuous release is updated, then a new one needs to be
# generated to update this script.
#if ! test -f "$FILE"; then
# echo "Downloading appimagetool for architecture '$ARCH'"
# wget -O appimagetool.AppImage https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage
#fi
#if [ $(shasum -a 256 appimagetool.AppImage | cut -d' ' -f1) = "46fdd785094c7f6e545b61afcfb0f3d98d8eab243f644b4b17698c01d06083d1" ] ; then
# PASSED_CHECKSUM="true"
#fi
;;
aarch64)
# Currently set to the continuous release channel until a static copy is put in 'ext-linux-bin'.
# The checksum will pass until the continuous release is updated, then a new one needs to be
# generated to update this script.
if ! test -f "$FILE"; then
echo "Downloading appimagetool for architecture '$ARCH'"
wget -O appimagetool.AppImage https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-aarch64.AppImage
fi
if [ $(shasum -a 256 appimagetool.AppImage | cut -d' ' -f1) = "04f45ea45b5aa07bb2b071aed9dbf7a5185d3953b11b47358c1311f11ea94a96" ] ; then
PASSED_CHECKSUM="true"
fi
;;
*)
PASSED_CHECKSUM="invalid_arch"
;;
esac
# delete the appimagetool downloaded if the checksum doesn't match.
if [ ! $PASSED_CHECKSUM == "true" ]; then
rm -f appimagetool.AppImage
fi
((DL_TRIES-=1))
done
if [ $PASSED_CHECKSUM == "true" ]; then
echo "Checksum passed. Proceeding to build image."
# Build AppImage
chmod a+x appimagetool.AppImage
./appimagetool.AppImage ./build "$2"
elif [ $PASSED_CHECKSUM == "invalid_arch" ]; then
echo "No download found for architecture '$ARCH'. Building halted."
else
echo "Checksum for appimagetool does not match. Building halted."
echo "If the file to be downloaded has been changed, a new checksum will need to be generated for this script."
fi
else
echo "QT not found, aborting AppImage build."
fi