diff --git a/.ci/package-appimage.sh b/.ci/package-appimage.sh index b2b9464fcc..37348a2e46 100755 --- a/.ci/package-appimage.sh +++ b/.ci/package-appimage.sh @@ -1,5 +1,9 @@ #!/bin/bash -ex +set -e +declare INSTALLABLES=`find build -type d -exec test -e '{}/cmake_install.cmake' ';' -print` +set +e + GITDATE="$(git show -s --date=short --format='%ad' | sed 's/-//g')" GITREV="$(git show -s --format='%h')" ARTIFACTS_DIR="$PWD/artifacts" @@ -44,6 +48,10 @@ create_appimage() { mkdir -p "${app_dir}/usr/optional/libstdc++" mkdir -p "${app_dir}/usr/optional/libgcc_s" + echo "Copying libraries..." + + for i in $INSTALLABLES; do cmake --install $i --prefix ${app_dir}/usr; done + if [ -d "build/bin/Release" ]; then cp "build/bin/Release/${binary_name}" "${app_dir}/usr/bin/" || echo "${binary_name} not found for AppDir" else diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5ba85bc315..3eb8bfe753 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -164,13 +164,13 @@ jobs: - name: Package AppImages run: | - ./.ci/package-appimage.sh "${{ github.ref_name }}" + ./AppImage-build-local.sh - name: Upload Linux artifacts uses: forgejo/upload-artifact@v4 with: name: linux.zip - path: artifacts/* + path: *.AppImage android: runs-on: android diff --git a/AppImage-build-debian-inner.sh b/AppImage-build-debian-inner.sh new file mode 100755 index 0000000000..34aa7252a7 --- /dev/null +++ b/AppImage-build-debian-inner.sh @@ -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 diff --git a/AppImage-build-debian.sh b/AppImage-build-debian.sh new file mode 100755 index 0000000000..73fd101775 --- /dev/null +++ b/AppImage-build-debian.sh @@ -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 torzu source dir +TORZU_SOURCE_DIR="$(realpath "$(dirname "${BASH_SOURCE[0]}")")" +echo "-> Source dir is $TORZU_SOURCE_DIR" +rm -rf "$TORZU_SOURCE_DIR/AppImageBuilder/build" + +# Generate debian rootfs +cd /tmp +echo "Cleaning up before build..." +rm -rf torzu-debian-appimage-rootfs +[ -d rootfs-torzu-appimage-build ] || + debootstrap stable rootfs-torzu-appimage-build http://deb.debian.org/debian/ +bwrap --bind rootfs-torzu-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 "$TORZU_SOURCE_DIR" /tmp/torzu-src-ro \ + --chdir / \ + --tmpfs /home \ + --setenv HOME /home \ + --bind /tmp /tmp/hosttmp \ + /tmp/torzu-src-ro/AppImage-build-debian-inner.sh +appimagetool torzu-debian-appimage-rootfs torzu.AppImage +echo "AppImage generated at /tmp/torzu.AppImage! Cleaning up..." +rm -rf torzu-debian-appimage-rootfs +if [ ! "$BUILD_KEEP_ROOTFS" = 1 ]; then + rm -rf rootfs-torzu-appimage-build +fi diff --git a/AppImage-build-local.sh b/AppImage-build-local.sh new file mode 100755 index 0000000000..a43f7001e5 --- /dev/null +++ b/AppImage-build-local.sh @@ -0,0 +1,42 @@ +#!/bin/bash + +FILE=build/bin/eden +if test -f "$FILE"; then + # remove any previously made AppImage in the base eden git folder + rm ./eden.AppImage + + # enter AppImage utility folder + cd AppImageBuilder + + # run the build script to create the AppImage + # (usage) ./build.sh [source eden build folder] [destination .AppImage file] + ./build.sh ../build ./eden.AppImage + + FILE=./eden.AppImage + if test -f "$FILE"; then + # move the AppImage to the main eden folder + mv eden.AppImage .. + # return to main eden folder + cd .. + # show contents of current folder + echo + ls + # show AppImages specifically + echo + ls *.AppImage + echo + echo "'eden.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 eden executable found in the /eden/build/bin folder!" + echo + echo "You must first build a native linux version of eden before running this script!" + echo +fi diff --git a/AppImageBuilder/appimagetool.AppImage b/AppImageBuilder/appimagetool.AppImage new file mode 100755 index 0000000000..2ba99ed549 Binary files /dev/null and b/AppImageBuilder/appimagetool.AppImage differ diff --git a/AppImageBuilder/assets/AppRun b/AppImageBuilder/assets/AppRun new file mode 100755 index 0000000000..5eb7435562 --- /dev/null +++ b/AppImageBuilder/assets/AppRun @@ -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 ./yuzu.sh "$@" +else + # Bundled Qt5 + exec ./yuzu-bqt.sh "$@" +fi diff --git a/AppImageBuilder/assets/eden-bqt.sh b/AppImageBuilder/assets/eden-bqt.sh new file mode 100755 index 0000000000..22c7c71e1e --- /dev/null +++ b/AppImageBuilder/assets/eden-bqt.sh @@ -0,0 +1,3 @@ +#! /bin/sh + +LD_LIBRARY_PATH=./qt5:/usr/lib/$(uname -m)-linux-gnu:/usr/lib:. QT_PLUGIN_PATH=./qt5 exec ./yuzu "$@" diff --git a/AppImageBuilder/assets/eden.sh b/AppImageBuilder/assets/eden.sh new file mode 100755 index 0000000000..7f25c932eb --- /dev/null +++ b/AppImageBuilder/assets/eden.sh @@ -0,0 +1,3 @@ +#! /bin/sh + +LD_LIBRARY_PATH=/usr/lib/$(uname -m)-linux-gnu:/usr/lib:. exec ./yuzu "$@" diff --git a/AppImageBuilder/assets_aarch64/.gitkeep b/AppImageBuilder/assets_aarch64/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/AppImageBuilder/assets_x86_64/bwrap b/AppImageBuilder/assets_x86_64/bwrap new file mode 100755 index 0000000000..a5dd85cc1f Binary files /dev/null and b/AppImageBuilder/assets_x86_64/bwrap differ diff --git a/AppImageBuilder/assets_x86_64/bwrap-info.txt b/AppImageBuilder/assets_x86_64/bwrap-info.txt new file mode 100644 index 0000000000..0178b387d9 --- /dev/null +++ b/AppImageBuilder/assets_x86_64/bwrap-info.txt @@ -0,0 +1 @@ +This is a statically compiled bubblewrap 0.9.0 executable. diff --git a/AppImageBuilder/build.sh b/AppImageBuilder/build.sh new file mode 100755 index 0000000000..69c9c99910 --- /dev/null +++ b/AppImageBuilder/build.sh @@ -0,0 +1,176 @@ +#! /bin/bash +set -e + +# Check arguments +if [[ $# != 2 ]]; then + >&2 echo "Bad usage!" + echo "Usage: $0 " + exit 1 +fi + +# Get paths +ARCH="$(uname -m)" +SYSTEM_LIBS="/usr/lib" +SYSTEM_LIBS64="/usr/lib64" +EDEN_BIN="${1}/bin" +EDEN_BIN_GUI="${EDEN_BIN}/eden" + +# Make sure executable exists +if [[ $(file -b --mime-type "$EDEN_BIN_GUI") != application/x-pie-executable ]]; then + >&2 echo "Invalid eden 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 eden 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 eden 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 "$EDEN_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 "$EDEN_BIN_GUI" ./build/ + + cat > "build/org.eden_emu.eden.desktop" << EOL +[Desktop Entry] +Type=Application +Name=Eden +Icon=org.eden_emu.eden +Exec=eden +Categories=Game;Emulator; +EOL + + cp "../dist/eden.svg" build/org.eden_emu.eden.svg + + # 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