Revert "build(linux): add pkgforge AppImage builder, fix aarch64/Qt/libusb, and clean up obsolete scripts"

This commit is contained in:
cheezwiz7899
2026-07-03 14:15:35 +10:00
committed by GitHub
parent 8c795a06e0
commit b520dd159b
18 changed files with 2008 additions and 5059 deletions
+435
View File
@@ -0,0 +1,435 @@
# GitLab CI config for Citron - DISABLED
# This CI configuration has been disabled as GitHub Actions is now being used
# To re-enable, uncomment the configuration below
# stages:
# - build
# - test
# - package
# variables:
# BUILD_TYPE: "Release"
# CMAKE_BUILD_PARALLEL_LEVEL: "4"
# DOCKER_DRIVER: overlay2
# DOCKER_TLS_CERTDIR: ""
# # Cache configuration for faster builds
# cache:
# paths:
# - build/vcpkg_installed/
# - build/_deps/
# key: "$CI_COMMIT_REF_SLUG"
# policy: pull-push
# # Linux Build (CachyOS optimized)
# build-linux:
# stage: build
# image: cachyos/cachyos:latest
# tags:
# - citron-build
# before_script:
# - pacman -Syu --noconfirm
# - pacman -S --noconfirm alsa-lib base-devel boost boost-libs catch2 cmake curl ffmpeg fmt fuse2 gamemode gcc gdb git glslang glu libusb libxi libxkbcommon libxkbcommon-x11 libxss libzip lz4 mbedtls2 mesa nasm ninja nlohmann-json openal openssl opus perl pipewire-audio pulseaudio pulseaudio-alsa qt6-base qt6-multimedia qt6-tools qt6-wayland sdl2 sdl2-compat unzip vulkan-headers vulkan-icd-loader vulkan-mesa-layers wget xcb-util-cursor xcb-util-image xcb-util-keysyms xcb-util-renderutil xcb-util-wm xorg-server-xvfb zip zstd zsync
# - echo '#!/bin/bash' > /usr/local/bin/shasum
# - echo 'if [ "$1" = "-a" ] && [ "$2" = "256" ]; then' >> /usr/local/bin/shasum
# - echo ' shift 2' >> /usr/local/bin/shasum
# - echo ' sha256sum "$@"' >> /usr/local/bin/shasum
# - echo 'else' >> /usr/local/bin/shasum
# - echo ' sha256sum "$@"' >> /usr/local/bin/shasum
# - echo 'fi' >> /usr/local/bin/shasum
# - chmod +x /usr/local/bin/shasum
# script: |
# # Clean and reset submodules to handle any inconsistencies
# git submodule deinit --all --force || true
# # Forcefully remove stale lock files that can block submodule updates
# find .git/modules -type f -name "*.lock" -delete
# git submodule update --init --recursive --force
# mkdir -p build && cd build
# # Dynamically find the Qt private header path
# HEADER_PATH=$(pacman -Ql qt6-base | grep 'qpa/qplatformnativeinterface.h$' | awk '{print $2}')
# if [ -z "$HEADER_PATH" ]; then echo "ERROR: Could not find qplatformnativeinterface.h path." >&2; exit 1; fi
# QT_PRIVATE_INCLUDE_DIR=$(dirname "$(dirname "$HEADER_PATH")")
# echo "Found Qt private include directory: ${QT_PRIVATE_INCLUDE_DIR}"
# # Configure and build with the dynamically found path
# cmake .. \
# -G Ninja \
# -DCMAKE_C_COMPILER=gcc \
# -DCMAKE_CXX_COMPILER=g++ \
# -DCMAKE_BUILD_TYPE=Release \
# -DCMAKE_INSTALL_PREFIX=/usr \
# -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \
# -DUSE_SYSTEM_QT=ON \
# -DCMAKE_C_FLAGS="-O3 -ffast-math -funsafe-math-optimizations -fno-math-errno -fno-trapping-math -freciprocal-math -ffp-contract=fast -fassociative-math -fno-rounding-math -fno-signaling-nans -march=x86-64-v2 -mtune=native -fomit-frame-pointer -fstrict-aliasing -frename-registers -fno-plt -funroll-loops -floop-interchange -floop-strip-mine -floop-block -fprefetch-loop-arrays -fpredictive-commoning -ftree-vectorize -fsplit-paths -finline-functions -finline-limit=1000 -finline-functions-called-once -fgcse-sm -fgcse-las -freorder-blocks-and-partition -fipa-pta -fipa-cp-clone -fwhole-program -flto=auto -fuse-linker-plugin -fvisibility=hidden -fdata-sections -ffunction-sections -fno-semantic-interposition -fopenmp -fexceptions -frtti" \
# -DCMAKE_CXX_FLAGS="-I${QT_PRIVATE_INCLUDE_DIR} -O3 -ffast-math -funsafe-math-optimizations -fno-math-errno -fno-trapping-math -freciprocal-math -ffp-contract=fast -fassociative-math -fno-rounding-math -fno-signaling-nans -march=x86-64-v2 -mtune=native -fomit-frame-pointer -fstrict-aliasing -frename-registers -fno-plt -funroll-loops -floop-interchange -floop-strip-mine -floop-block -fprefetch-loop-arrays -fpredictive-commoning -ftree-vectorize -fsplit-paths -finline-functions -finline-limit=1000 -finline-functions-called-once -fgcse-sm -fgcse-las -freorder-blocks-and-partition -fipa-pta -fipa-cp-clone -fwhole-program -flto=auto -fuse-linker-plugin -fvisibility=hidden -fdata-sections -ffunction-sections -fno-semantic-interposition -fopenmp -fexceptions -frtti -Wno-error -w" \
# -DCMAKE_EXE_LINKER_FLAGS="-Wl,--gc-sections -Wl,-O2 -Wl,--as-needed" \
# -DSDL_PIPEWIRE=OFF \
# -DCITRON_USE_BUNDLED_VCPKG=OFF \
# -DUSE_DISCORD_PRESENCE=OFF \
# -DBUNDLE_SPEEX=ON \
# -DCITRON_USE_BUNDLED_FFMPEG=OFF \
# -DCITRON_USE_QT_MULTIMEDIA=OFF \
# -DCITRON_USE_QT_WEB_ENGINE=OFF
# ninja
# cd ..
# echo "Build completed, checking for executables..."
# ls -la build/bin/ || echo "No build/bin directory found"
# find build/ -name "*.so" -o -name "citron*" | head -20 || echo "No shared libraries or executables found"
# echo "Checking library dependencies..."
# ldd build/bin/citron || echo "citron binary not found or ldd failed"
# chmod +x AppImage-build-local.sh
# echo "Attempting AppImage build with FUSE workaround..."
# export APPIMAGE_EXTRACT_AND_RUN=1
# export ARCH=x86_64
# export VERSION=$(git describe --tags --always)
# echo "Testing shasum compatibility..."
# which shasum || echo "shasum not found in PATH"
# echo "Testing shasum -a 256 syntax..."
# echo "test" | shasum -a 256 || echo "shasum -a 256 failed"
# echo "test" | sha256sum || echo "sha256sum failed"
# echo "Running AppImage build..."
# ./AppImage-build-local.sh || echo "AppImage build failed, continuing..."
# ls -la *.AppImage || echo "No AppImage files found"
# echo "AppImage size information:"
# du -h *.AppImage || echo "No AppImage to measure"
# echo "Total artifact size:"
# du -sh citron.AppImage build/bin/citron 2>/dev/null || echo "Cannot measure artifact sizes"
# echo "Compressing AppImage for upload..."
# gzip -9 -c citron.AppImage > citron.AppImage.gz
# ls -la citron.AppImage.gz
# du -h citron.AppImage.gz
# echo "AppImage compressed with gzip successfully"
# tar -czf citron-binary.tar.gz build/bin/
# ls -la citron-binary.tar.gz
# du -h citron-binary.tar.gz
# artifacts:
# paths:
# - citron.AppImage.gz
# - citron-binary.tar.gz
# expire_in: 1 week
# when: always
# only:
# - main
# - master
# - develop
# - ci-fixes
# - Boss-Build-Flags-For-CI-CMAKE-ON-CachyOS
# - merge_requests
# # Linux Build (CachyOS v3 optimized)
# build-linux-v3:
# stage: build
# image: cachyos/cachyos-v3
# tags:
# - citron-build
# before_script:
# - pacman -Syu --noconfirm
# - pacman -S --noconfirm alsa-lib base-devel boost boost-libs catch2 cmake curl ffmpeg fmt fuse2 gamemode gcc gdb git glslang glu libusb libxi libxkbcommon libxkbcommon-x11 libxss libzip lz4 mbedtls2 mesa nasm ninja nlohmann-json openal openssl opus perl pipewire-audio pulseaudio pulseaudio-alsa python-pip qt6-base qt6-multimedia qt6-tools qt6-wayland sdl2 sdl2-compat unzip vulkan-headers vulkan-icd-loader vulkan-mesa-layers wget xcb-util-cursor xcb-util-image xcb-util-keysyms xcb-util-renderutil xcb-util-wm xorg-server-xvfb zip zstd zsync
# - echo '#!/bin/bash' > /usr/local/bin/shasum
# - echo 'if [ "$1" = "-a" ] && [ "$2" = "256" ]; then' >> /usr/local/bin/shasum
# - echo ' shift 2' >> /usr/local/bin/shasum
# - echo ' sha256sum "$@"' >> /usr/local/bin/shasum
# - echo 'else' >> /usr/local/bin/shasum
# - echo ' sha256sum "$@"' >> /usr/local/bin/shasum
# - echo 'fi' >> /usr/local/bin/shasum
# - chmod +x /usr/local/bin/shasum
# script: |
# git submodule deinit --all --force || true
# find .git/modules -type f -name "*.lock" -delete
# git submodule update --init --recursive --force
# mkdir -p build && cd build
# HEADER_PATH=$(pacman -Ql qt6-base | grep 'qpa/qplatformnativeinterface.h$' | awk '{print $2}')
# if [ -z "$HEADER_PATH" ]; then echo "ERROR: Could not find qplatformnativeinterface.h path." >&2; exit 1; fi
# QT_PRIVATE_INCLUDE_DIR=$(dirname "$(dirname "$HEADER_PATH")")
# echo "Found Qt private include directory: ${QT_PRIVATE_INCLUDE_DIR}"
# cmake .. \
# -G Ninja \
# -DCMAKE_C_COMPILER=gcc \
# -DCMAKE_CXX_COMPILER=g++ \
# -DCMAKE_BUILD_TYPE=Release \
# -DCMAKE_INSTALL_PREFIX=/usr \
# -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \
# -DCMAKE_C_FLAGS="-O3 -ffast-math -funsafe-math-optimizations -fno-math-errno -fno-trapping-math -freciprocal-math -ffp-contract=fast -fassociative-math -fno-rounding-math -fno-signaling-nans -march=x86-64-v3 -mtune=native -fomit-frame-pointer -fstrict-aliasing -frename-registers -fno-plt -funroll-loops -floop-interchange -floop-strip-mine -floop-block -fprefetch-loop-arrays -fpredictive-commoning -ftree-vectorize -fsplit-paths -finline-functions -finline-limit=1000 -finline-functions-called-once -fgcse-sm -fgcse-las -freorder-blocks-and-partition -fipa-pta -fipa-cp-clone -fwhole-program -flto=auto -fuse-linker-plugin -fvisibility=hidden -fdata-sections -ffunction-sections -fno-semantic-interposition -fopenmp -fexceptions -frtti" \
# -DCMAKE_CXX_FLAGS="-I${QT_PRIVATE_INCLUDE_DIR} -O3 -ffast-math -funsafe-math-optimizations -fno-math-errno -fno-trapping-math -freciprocal-math -ffp-contract=fast -fassociative-math -fno-rounding-math -fno-signaling-nans -march=x86-64-v3 -mtune=native -fomit-frame-pointer -fstrict-aliasing -frename-registers -fno-plt -funroll-loops -floop-interchange -floop-strip-mine -floop-block -fprefetch-loop-arrays -fpredictive-commoning -ftree-vectorize -fsplit-paths -finline-functions -finline-limit=1000 -finline-functions-called-once -fgcse-sm -fgcse-las -freorder-blocks-and-partition -fipa-pta -fipa-cp-clone -fwhole-program -flto=auto -fuse-linker-plugin -fvisibility=hidden -fdata-sections -ffunction-sections -fno-semantic-interposition -fopenmp -fexceptions -frtti -Wno-error -w" \
# -DCMAKE_EXE_LINKER_FLAGS="-Wl,--gc-sections -Wl,-O2 -Wl,--as-needed" \
# -DSDL_PIPEWIRE=OFF \
# -DCITRON_USE_BUNDLED_VCPKG=OFF \
# -DUSE_DISCORD_PRESENCE=OFF \
# -DBUNDLE_SPEEX=ON \
# -DCITRON_USE_BUNDLED_FFMPEG=OFF \
# -DCITRON_USE_QT_MULTIMEDIA=OFF \
# -DCITRON_USE_QT_WEB_ENGINE=OFF
# ninja
# cd ..
# echo "Build completed, checking for executables..."
# ls -la build/bin/ || echo "No build/bin directory found"
# find build/ -name "*.so" -o -name "citron*" | head -20 || echo "No shared libraries or executables found"
# echo "Checking library dependencies..."
# ldd build/bin/citron || echo "citron binary not found or ldd failed"
# chmod +x AppImage-build-local.sh
# echo "Attempting AppImage build with FUSE workaround..."
# export APPIMAGE_EXTRACT_AND_RUN=1
# export ARCH=x86_64
# export VERSION=$(git describe --tags --always)
# echo "Testing shasum compatibility..."
# which shasum || echo "shasum not found in PATH"
# echo "Testing shasum -a 256 syntax..."
# echo "test" | shasum -a 256 || echo "shasum -a 256 failed"
# echo "test" | sha256sum || echo "sha256sum failed"
# echo "Running AppImage build..."
# ./AppImage-build-local.sh || echo "AppImage build failed, continuing..."
# ls -la *.AppImage || echo "No AppImage files found"
# echo "AppImage size information:"
# du -h *.AppImage || echo "No AppImage to measure"
# echo "Total artifact size:"
# du -sh citron.AppImage build/bin/citron 2>/dev/null || echo "Cannot measure artifact sizes"
# echo "Compressing AppImage for upload..."
# gzip -9 -c citron.AppImage > citron.AppImage.gz
# ls -la citron.AppImage.gz
# du -h citron.AppImage.gz
# echo "AppImage compressed with gzip successfully"
# tar -czf citron-binary.tar.gz build/bin/
# ls -la citron-binary.tar.gz
# du -h citron-binary.tar.gz
# artifacts:
# paths:
# - citron.AppImage.gz
# - citron-binary.tar.gz
# expire_in: 1 week
# when: always
# only:
# - main
# - master
# - develop
# - ci-fixes
# - Boss-Build-Flags-For-CI-CMAKE-ON-CachyOS
# - merge_requests
# # Linux Build (Steam Deck optimized - AMD Zen 2)
# build-linux-steamdeck:
# stage: build
# image: cachyos/cachyos:latest
# tags:
# - citron-build
# before_script:
# - pacman -Syu --noconfirm
# - pacman -S --noconfirm alsa-lib base-devel boost boost-libs catch2 cmake curl ffmpeg fmt fuse2 gamemode gcc gdb git glslang glu libusb libxi libxkbcommon libxkbcommon-x11 libxss libzip lz4 mbedtls2 mesa nasm ninja nlohmann-json openal openssl opus perl pipewire-audio pulseaudio pulseaudio-alsa python-pip qt6-base qt6-multimedia qt6-tools qt6-wayland sdl2 sdl2-compat unzip vulkan-headers vulkan-icd-loader vulkan-mesa-layers wget xcb-util-cursor xcb-util-image xcb-util-keysyms xcb-util-renderutil xcb-util-wm xorg-server-xvfb zip zstd zsync
# - echo '#!/bin/bash' > /usr/local/bin/shasum
# - echo 'if [ "$1" = "-a" ] && [ "$2" = "256" ]; then' >> /usr/local/bin/shasum
# - echo ' shift 2' >> /usr/local/bin/shasum
# - echo ' sha256sum "$@"' >> /usr/local/bin/shasum
# - echo 'else' >> /usr/local/bin/shasum
# - echo ' sha256sum "$@"' >> /usr/local/bin/shasum
# - echo 'fi' >> /usr/local/bin/shasum
# - chmod +x /usr/local/bin/shasum
# script: |
# git submodule deinit --all --force || true
# find .git/modules -type f -name "*.lock" -delete
# git submodule update --init --recursive --force
# mkdir -p build && cd build
# HEADER_PATH=$(pacman -Ql qt6-base | grep 'qpa/qplatformnativeinterface.h$' | awk '{print $2}')
# if [ -z "$HEADER_PATH" ]; then echo "ERROR: Could not find qplatformnativeinterface.h path." >&2; exit 1; fi
# QT_PRIVATE_INCLUDE_DIR=$(dirname "$(dirname "$HEADER_PATH")")
# echo "Found Qt private include directory: ${QT_PRIVATE_INCLUDE_DIR}"
# cmake .. \
# -G Ninja \
# -DCMAKE_C_COMPILER=gcc \
# -DCMAKE_CXX_COMPILER=g++ \
# -DCMAKE_BUILD_TYPE=Release \
# -DCMAKE_INSTALL_PREFIX=/usr \
# -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \
# -DCMAKE_C_FLAGS="-O3 -ffast-math -funsafe-math-optimizations -fno-math-errno -fno-trapping-math -freciprocal-math -ffp-contract=fast -fassociative-math -fno-rounding-math -fno-signaling-nans -march=znver2 -mtune=znver2 -fomit-frame-pointer -fstrict-aliasing -frename-registers -fno-plt -funroll-loops -floop-interchange -floop-strip-mine -floop-block -fprefetch-loop-arrays -fpredictive-commoning -ftree-vectorize -fsplit-paths -finline-functions -finline-limit=1000 -finline-functions-called-once -fgcse-sm -fgcse-las -freorder-blocks-and-partition -fipa-pta -fipa-cp-clone -fwhole-program -flto=auto -fuse-linker-plugin -fvisibility=hidden -fdata-sections -ffunction-sections -fno-semantic-interposition -fopenmp -fexceptions -frtti" \
# -DCMAKE_CXX_FLAGS="-I${QT_PRIVATE_INCLUDE_DIR} -O3 -ffast-math -funsafe-math-optimizations -fno-math-errno -fno-trapping-math -freciprocal-math -ffp-contract=fast -fassociative-math -fno-rounding-math -fno-signaling-nans -march=znver2 -mtune=znver2 -fomit-frame-pointer -fstrict-aliasing -frename-registers -fno-plt -funroll-loops -floop-interchange -floop-strip-mine -floop-block -fprefetch-loop-arrays -fpredictive-commoning -ftree-vectorize -fsplit-paths -finline-functions -finline-limit=1000 -finline-functions-called-once -fgcse-sm -fgcse-las -freorder-blocks-and-partition -fipa-pta -fipa-cp-clone -fwhole-program -flto=auto -fuse-linker-plugin -fvisibility=hidden -fdata-sections -ffunction-sections -fno-semantic-interposition -fopenmp -fexceptions -frtti -Wno-error -w" \
# -DCMAKE_EXE_LINKER_FLAGS="-Wl,--gc-sections -Wl,-O2 -Wl,--as-needed" \
# -DSDL_PIPEWIRE=OFF \
# -DCITRON_USE_BUNDLED_VCPKG=OFF \
# -DUSE_DISCORD_PRESENCE=OFF \
# -DBUNDLE_SPEEX=ON \
# -DCITRON_USE_BUNDLED_FFMPEG=OFF \
# -DCITRON_USE_QT_MULTIMEDIA=OFF \
# -DCITRON_USE_QT_WEB_ENGINE=OFF
# ninja
# cd ..
# echo "Build completed, checking for executables..."
# ls -la build/bin/ || echo "No build/bin directory found"
# find build/ -name "*.so" -o -name "citron*" | head -20 || echo "No shared libraries or executables found"
# echo "Checking library dependencies..."
# ldd build/bin/citron || echo "citron binary not found or ldd failed"
# chmod +x AppImage-build-local.sh
# echo "Attempting AppImage build with FUSE workaround..."
# export APPIMAGE_EXTRACT_AND_RUN=1
# export ARCH=x86_64
# export VERSION=$(git describe --tags --always)
# echo "Testing shasum compatibility..."
# which shasum || echo "shasum not found in PATH"
# echo "Testing shasum -a 256 syntax..."
# echo "test" | shasum -a 256 || echo "shasum -a 256 failed"
# echo "test" | sha256sum || echo "sha256sum failed"
# echo "Running AppImage build..."
# ./AppImage-build-local.sh || echo "AppImage build failed, continuing..."
# ls -la *.AppImage || echo "No AppImage files found"
# echo "AppImage size information:"
# du -h *.AppImage || echo "No AppImage to measure"
# echo "Total artifact size:"
# du -sh citron.AppImage build/bin/citron 2>/dev/null || echo "Cannot measure artifact sizes"
# echo "Compressing AppImage for upload..."
# gzip -9 -c citron.AppImage > citron.AppImage.gz
# ls -la citron.AppImage.gz
# du -h citron.AppImage.gz
# echo "AppImage compressed with gzip successfully"
# tar -czf citron-binary.tar.gz build/bin/
# ls -la citron-binary.tar.gz
# du -h citron-binary.tar.gz
# artifacts:
# paths:
# - citron.AppImage.gz
# - citron-binary.tar.gz
# expire_in: 1 week
# when: always
# only:
# - main
# - master
# - develop
# - ci-fixes
# - Boss-Build-Flags-For-CI-CMAKE-ON-CachyOS
# - merge_requests
# # Android Build (Ubuntu 24.04 optimized)
# build-android:
# stage: build
# image: ubuntu:24.04
# tags:
# - citron-build
# before_script:
# - apt-get update -qq
# - apt-get install -y -qq openjdk-17-jdk wget unzip curl git cmake build-essential pkg-config zip glslang-tools
# - wget -q https://dl.google.com/android/repository/commandlinetools-linux-11076708_latest.zip
# - unzip -q commandlinetools-linux-11076708_latest.zip
# - mkdir -p android-sdk/cmdline-tools
# - mv cmdline-tools android-sdk/cmdline-tools/latest
# - export ANDROID_SDK_ROOT=$PWD/android-sdk
# - export PATH=$PATH:$ANDROID_SDK_ROOT/cmdline-tools/latest/bin
# - printf "y\ny\ny\ny\ny\ny\ny\n" | sdkmanager --licenses || true
# - sdkmanager "platform-tools" "platforms;android-34" "build-tools;34.0.0"
# - wget -q https://dl.google.com/android/repository/android-ndk-r26b-linux.zip
# - unzip -q android-ndk-r26b-linux.zip
# - export ANDROID_NDK_HOME=$PWD/android-ndk-r26b
# script:
# - git submodule deinit --all --force || true
# - git submodule update --init --recursive --force
# - cmake -B build-android -S . -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK_HOME/build/cmake/android.toolchain.cmake -DANDROID_ABI=arm64-v8a -DANDROID_PLATFORM=android-30 -DENABLE_QT=OFF -DENABLE_SDL2=OFF -DENABLE_WEB_SERVICE=OFF -DCITRON_USE_BUNDLED_VCPKG=ON -DCITRON_USE_BUNDLED_FFMPEG=ON -DANDROID_ARM_NEON=ON -DCITRON_ENABLE_LTO=ON
# - cmake --build build-android --config $BUILD_TYPE --parallel $CMAKE_BUILD_PARALLEL_LEVEL
# - cd src/android
# - ./gradlew assembleMainlineRelease
# artifacts:
# paths:
# - src/android/app/build/outputs/apk/mainline/release/*.apk
# expire_in: 1 week
# only:
# - main
# - master
# - develop
# - ci-fixes
# - merge_requests
# allow_failure: true
# # Unit Tests
# test-unit:
# stage: test
# image: ubuntu:24.04
# tags:
# - citron-build
# before_script:
# # Set the environment to be non-interactive
# - export DEBIAN_FRONTEND=noninteractive
#
# # Update package lists and install tool for managing repositories
# - apt-get update -qq
# - apt-get install -y -qq software-properties-common
#
# # Reliably add the 'universe' and 'multiverse' repositories
# - add-apt-repository universe
# - add-apt-repository multiverse
#
# # Update the package lists again to include the new repositories
# - apt-get update -qq
#
# # Install all required system packages for the build
# - apt-get install -y -qq
# build-essential
# cmake
# ninja-build
# git
# pkg-config
# catch2
# libasound2-dev
# libpulse-dev
# libboost-all-dev
# libavcodec-dev
# libavfilter-dev
# libavformat-dev
# libavutil-dev
# libswscale-dev
# llvm-17-dev
# gamemode-dev
# libenet-dev
# libfmt-dev
# liblz4-dev
# libopus-dev
# libssl-dev
# libusb-1.0-0-dev
# libvulkan-dev
# nlohmann-json3-dev
# zlib1g-dev
# libzstd-dev
# libopenal-dev
# libedit-dev
# libcurl4-openssl-dev
# libstb-dev
# libcubeb-dev
# libspeexdsp-dev
# libva-dev
# glslang-tools
# libx11-dev
#
# script:
# - git submodule deinit --all --force || true
# - git submodule update --init --recursive --force
# - cmake -B build-test -S . -DCMAKE_BUILD_TYPE=Debug -DCITRON_TESTS=ON -DENABLE_QT=OFF -DENABLE_SDL2=OFF -DENABLE_WEB_SERVICE=OFF
# - cmake --build build-test --config Debug --parallel $CMAKE_BUILD_PARALLEL_LEVEL
# - cd build-test
# - ctest --output-on-failure
# artifacts:
# reports:
# junit: build-test/Testing/**/*.xml
# expire_in: 1 week
# only:
# - main
# - master
# - develop
# - ci-fixes
# - merge_requests
# # Package Release
# package-release:
# stage: package
# image: ubuntu:24.04
# tags:
# - citron-build
# dependencies:
# - build-linux
# script:
# - mkdir -p release
# - echo "Creating release package..."
# - tar -czf citron-release-$(date +%Y%m%d).tar.gz release/ || true
# artifacts:
# paths:
# - citron-release-*.tar.gz
# expire_in: 1 month
# only:
# - main
# - master
# - develop
# - ci-fixes
# - tags
+83
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
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
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
+104
View File
@@ -0,0 +1,104 @@
#!/bin/bash
set -e
# Get script directory
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
BUILD_DIR_RAW="${1:-$SCRIPT_DIR/../build/generate}"
BUILD_DIR="$(readlink -f "${BUILD_DIR_RAW}")"
# Save environment overrides
ENV_QT_PATH="${CITRON_QT_PATH:-}"
ENV_ICU_PATH="${CITRON_ICU_PATH:-}"
ENV_XCB_PATH="${CITRON_XCB_PATH:-}"
ENV_BINARY_DIR="${CITRON_BINARY_DIR:-}"
if [ ! -f "$BUILD_DIR/AppImageBuilder/config.sh" ]; then
echo "Error: config.sh not found in $BUILD_DIR/AppImageBuilder/"
echo "Please run CMake first."
exit 1
fi
source "$BUILD_DIR/AppImageBuilder/config.sh"
# Allow environment variables to override config.sh
export CITRON_QT_PATH="${ENV_QT_PATH:-$CITRON_QT_PATH}"
export CITRON_ICU_PATH="${ENV_ICU_PATH:-$CITRON_ICU_PATH}"
export CITRON_XCB_PATH="${ENV_XCB_PATH:-$CITRON_XCB_PATH}"
export CITRON_BINARY_DIR="${ENV_BINARY_DIR:-$CITRON_BINARY_DIR}"
# Tools setup
TOOLS_DIR="$BUILD_DIR/AppImageBuilder/tools"
mkdir -p "$TOOLS_DIR"
LINUXDEPLOY="$TOOLS_DIR/linuxdeploy-x86_64.AppImage"
LINUXDEPLOY_QT="$TOOLS_DIR/linuxdeploy-plugin-qt-x86_64.AppImage"
download_tool() {
local url=$1
local file=$2
if [ ! -f "$file" ]; then
echo "Downloading $file..."
wget -q --show-progress "$url" -O "$file"
chmod +x "$file"
fi
}
download_tool "https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage" "$LINUXDEPLOY"
download_tool "https://github.com/linuxdeploy/linuxdeploy-plugin-qt/releases/download/continuous/linuxdeploy-plugin-qt-x86_64.AppImage" "$LINUXDEPLOY_QT"
# AppDir setup
APPDIR="$BUILD_DIR/AppImageBuilder/AppDir"
rm -rf "$APPDIR"
mkdir -p "$APPDIR"
# Build the AppImage
# We need to tell linuxdeploy where our custom libraries are
export LD_LIBRARY_PATH="$CITRON_ICU_PATH:$CITRON_XCB_PATH/lib:$LD_LIBRARY_PATH"
export EXTRA_QT_PLUGINS="wayland" # Add any extra plugins needed
export NO_STRIP=1 # Avoid strip failures on modern binaries (SHT_RELR)
# Run linuxdeploy
# --plugin qt handles Qt dependencies
export QMAKE="$CITRON_QT_PATH/bin/qmake"
export QT_ROOT="$CITRON_QT_PATH"
export LD_LIBRARY_PATH="$CITRON_QT_PATH/lib:$CITRON_ICU_PATH:$CITRON_XCB_PATH/lib:${LD_LIBRARY_PATH:-}"
pushd "$BUILD_DIR" > /dev/null
# Run linuxdeploy to populate AppDir
"$LINUXDEPLOY" --appdir "$APPDIR" \
--executable "$CITRON_BINARY_DIR/citron" \
--desktop-file "$SCRIPT_DIR/assets/citron.desktop" \
--icon-file "$SCRIPT_DIR/assets/citron.svg" \
--library "$CITRON_ICU_PATH/libicuuc.so.73" \
--library "$CITRON_ICU_PATH/libicui18n.so.73" \
--library "$CITRON_ICU_PATH/libicudata.so.73" \
--library "$CITRON_XCB_PATH/lib/libxcb.so.1" \
--library "$CITRON_XCB_PATH/lib/libxcb-cursor.so.0" \
--library "$CITRON_XCB_PATH/lib/libxcb-xkb.so.1" \
--library "$CITRON_XCB_PATH/lib/libxcb-render.so.0" \
--library "$CITRON_XCB_PATH/lib/libxcb-render-util.so.0" \
--library "$CITRON_XCB_PATH/lib/libxcb-shape.so.0" \
--library "$CITRON_XCB_PATH/lib/libxcb-shm.so.0" \
--library "$CITRON_XCB_PATH/lib/libxcb-sync.so.1" \
--library "$CITRON_XCB_PATH/lib/libxcb-xfixes.so.0" \
--library "$CITRON_XCB_PATH/lib/libxcb-randr.so.0" \
--library "$CITRON_XCB_PATH/lib/libxcb-image.so.0" \
--library "$CITRON_XCB_PATH/lib/libxcb-keysyms.so.1" \
--library "$CITRON_XCB_PATH/lib/libxcb-icccm.so.4" \
--library "$CITRON_XCB_PATH/lib/libXau.so.6" \
--library "$CITRON_XCB_PATH/lib/libXdmcp.so.6" \
--plugin qt
# Inject the wrapper script to support portable mode (user folder) and PGO profiles
# We rename the real binary to citron.bin and put the script as 'citron'
if [ -f "$APPDIR/usr/bin/citron" ]; then
mv "$APPDIR/usr/bin/citron" "$APPDIR/usr/bin/citron.bin"
cp "$SCRIPT_DIR/assets/citron.sh" "$APPDIR/usr/bin/citron"
chmod +x "$APPDIR/usr/bin/citron"
fi
# Build the final AppImage
"$LINUXDEPLOY" --appdir "$APPDIR" --output appimage
popd > /dev/null
echo "AppImage build complete in $BUILD_DIR"
+170
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
-410
View File
@@ -1,410 +0,0 @@
#!/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}"
File diff suppressed because it is too large Load Diff
+1 -11
View File
@@ -604,17 +604,7 @@ if (ENABLE_QT AND NOT CITRON_USE_CPM)
message(STATUS "Using host Qt at ${QT_HOST_PATH}")
endif()
# Early Qt6 discovery: find Widgets and Concurrent unconditionally; add
# Multimedia only when CITRON_USE_QT_MULTIMEDIA is ON. This makes the
# cmake flag actually functional: with CITRON_USE_QT_MULTIMEDIA=OFF the
# Multimedia module and its shared FFmpeg backend (libffmpegmediaplugin.so,
# libavcodec.so.61 ~80 MB, etc.) are not linked at all.
set(_qt_probe_components Widgets Concurrent)
if (CITRON_USE_QT_MULTIMEDIA)
list(APPEND _qt_probe_components Multimedia)
endif()
find_package(Qt6 REQUIRED COMPONENTS ${_qt_probe_components})
unset(_qt_probe_components)
find_package(Qt6 REQUIRED COMPONENTS Widgets Multimedia Concurrent)
if (UNIX AND NOT APPLE)
find_package(Qt6 REQUIRED COMPONENTS DBus)
+22 -32
View File
@@ -9,8 +9,7 @@
#
# Target variants:
# Windows (native or cross-compile target) → win64_llvm_mingw
# Linux native x86-64 → linux_gcc_64 (via aqt)
# Linux native aarch64 → linux_gcc_arm64 (host: linux_arm64)
# Linux native → linux_gcc_64
#
# Cross-compilation (Linux host → Windows target):
# QT_HOST_PATH is set to a Linux Qt install so moc/rcc/uic run on the host.
@@ -71,22 +70,12 @@ else()
set(_QT_DIR_NAME "macos")
set(_QT_CMAKE_SUB "lib/cmake/Qt6")
else()
# Native Linux — pick host/arch variant to match the build processor so
# moc/rcc/uic (which run on the build host) are the correct ELF arch.
# aqt uses separate host-OS strings for x86-64 ("linux") and arm64
# ("linux_arm64"); the arch token is then linux_gcc_64 or linux_gcc_arm64.
# Native Linux
set(_QT_OS "linux")
set(_QT_TARGET "desktop")
set(_QT_ARCH "linux_gcc_64")
set(_QT_DIR_NAME "gcc_64")
set(_QT_CMAKE_SUB "lib/cmake/Qt6")
if (CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "aarch64|arm64" OR ARCHITECTURE_arm64)
set(_QT_OS "linux_arm64")
set(_QT_TARGET "desktop")
set(_QT_ARCH "linux_gcc_arm64")
set(_QT_DIR_NAME "gcc_arm64")
else()
set(_QT_OS "linux")
set(_QT_TARGET "desktop")
set(_QT_ARCH "linux_gcc_64")
set(_QT_DIR_NAME "gcc_64")
endif()
endif()
endif()
@@ -123,29 +112,23 @@ else()
message(STATUS "[Qt] Qt ${CITRON_QT_VERSION} target downloaded")
endif()
# Download additional modules (imageformats, svg, tools).
# qtmultimedia is intentionally excluded: all Qt Multimedia camera code in
# citron is guarded by (QT_VERSION < 6.0.0) && CITRON_USE_QT_MULTIMEDIA,
# so it is permanently dead code under Qt6. CITRON_USE_QT_MULTIMEDIA=OFF
# is set in build-citron-linux.sh, but the Qt5-only guard makes it a no-op
# regardless. Including qtmultimedia in --modules was the sole cause of the
# recurring "[Qt] Additional module install failed" CI warning: aqt silently
# rejects module name mismatches for certain arch/version combos.
# Download additional modules (multimedia, imageformats, svg)
set(_QT_MM_CMAKE "${_QT_TARGET_DIR}/lib/cmake/Qt6Multimedia/Qt6MultimediaConfig.cmake")
set(_QT_SVG_CMAKE "${_QT_TARGET_DIR}/lib/cmake/Qt6Svg/Qt6SvgConfig.cmake")
set(_QT_TOOL_CMAKE "${_QT_TARGET_DIR}/lib/cmake/Qt6CoreTools/Qt6CoreToolsConfig.cmake")
if (NOT EXISTS "${_QT_SVG_CMAKE}" OR NOT EXISTS "${_QT_TOOL_CMAKE}")
message(STATUS "[Qt] Downloading Qt ${CITRON_QT_VERSION} additional modules (qttools, qtimageformats)...")
if (NOT EXISTS "${_QT_MM_CMAKE}" OR NOT EXISTS "${_QT_SVG_CMAKE}" OR NOT EXISTS "${_QT_TOOL_CMAKE}")
message(STATUS "[Qt] Downloading Qt ${CITRON_QT_VERSION} additional modules...")
execute_process(
COMMAND ${_AQT_EXECUTABLE} install-qt
${_QT_OS} ${_QT_TARGET}
${CITRON_QT_VERSION} ${_QT_ARCH}
--outputdir "${CITRON_QT_BASE_DIR}"
--modules qttools qtimageformats
RESULT_VARIABLE _qt_addl_result
--modules qttools qtmultimedia qtimageformats
RESULT_VARIABLE _qt_mm_result
OUTPUT_QUIET ERROR_QUIET
)
if (NOT _qt_addl_result EQUAL 0)
message(WARNING "[Qt] Additional module install failed (qttools/qtimageformats) — build may fail")
if (NOT _qt_mm_result EQUAL 0)
message(WARNING "[Qt] Additional module install failed — build may fail")
endif()
endif()
@@ -189,7 +172,14 @@ if (CMAKE_HOST_UNIX AND WIN32)
if (NOT _qt_host_result EQUAL 0)
message(WARNING "[Qt] Host Qt download failed — cross-compile may fail")
endif()
# qtmultimedia not downloaded for host Qt — dead code under Qt6 (see target Qt comment above)
execute_process(
COMMAND ${_AQT_EXECUTABLE} install-qt linux desktop
${CITRON_QT_VERSION} linux_gcc_64
--outputdir "${CITRON_QT_BASE_DIR}"
--modules qtmultimedia
OUTPUT_QUIET ERROR_QUIET
)
endif()
if (EXISTS "${_QT_HOST_CMAKE}")
+44 -400
View File
File diff suppressed because it is too large Load Diff
+86
View File
@@ -0,0 +1,86 @@
@echo off
REM SPDX-FileCopyrightText: 2026 citron Emulator Project
REM SPDX-License-Identifier: GPL-3.0-or-later
REM
REM One-click MinGW build launcher for Citron Neo.
REM Finds your MSYS2 installation and runs build-for-mingw.sh in UCRT64 mode.
setlocal enabledelayedexpansion
REM ---------- locate MSYS2 ----------
set "MSYS2_PATH="
REM Check common install locations
for %%P in (
"C:\msys64"
"C:\msys2"
"%USERPROFILE%\msys64"
"%USERPROFILE%\msys2"
"D:\msys64"
"D:\msys2"
) do (
if exist "%%~P\usr\bin\bash.exe" (
set "MSYS2_PATH=%%~P"
goto :found_msys2
)
)
REM Try the PATH
where bash.exe >nul 2>&1
if %ERRORLEVEL% equ 0 (
for /f "delims=" %%I in ('where bash.exe') do (
set "BASH_LOC=%%~dpI"
if exist "!BASH_LOC!..\..\..\usr\bin\bash.exe" (
for %%J in ("!BASH_LOC!..\..\..") do set "MSYS2_PATH=%%~fJ"
goto :found_msys2
)
)
)
echo.
echo ERROR: Could not find an MSYS2 installation.
echo.
echo Please install MSYS2 from https://www.msys2.org/ and try again,
echo or set MSYS2_PATH before running this script:
echo.
echo set MSYS2_PATH=C:\msys64
echo build-for-mingw.bat
echo.
pause
exit /b 1
:found_msys2
echo Found MSYS2 at: %MSYS2_PATH%
REM ---------- resolve script path ----------
set "SCRIPT_DIR=%~dp0"
REM Remove trailing backslash
if "%SCRIPT_DIR:~-1%"=="\" set "SCRIPT_DIR=%SCRIPT_DIR:~0,-1%"
REM Convert Windows path to MSYS2 path (C:\foo\bar -> /c/foo/bar)
set "MSYS_SOURCE=%SCRIPT_DIR:\=/%"
set "MSYS_SOURCE=/%MSYS_SOURCE:~0,1%%MSYS_SOURCE:~2%"
REM Lowercase the drive letter
for %%a in (a b c d e f g h i j k l m n o p q r s t u v w x y z) do (
set "MSYS_SOURCE=!MSYS_SOURCE:/%%a/=/%%a/!"
)
REM ---------- run the build ----------
echo.
echo Launching MSYS2 UCRT64 build ...
echo.
"%MSYS2_PATH%\usr\bin\env.exe" MSYSTEM=UCRT64 ^
"%MSYS2_PATH%\usr\bin\bash.exe" -lc ^
"cd '%MSYS_SOURCE%' && bash build-for-mingw.sh %*"
if %ERRORLEVEL% neq 0 (
echo.
echo Build failed with error code %ERRORLEVEL%.
pause
exit /b %ERRORLEVEL%
)
echo.
echo Build succeeded! Executables are in build-mingw\bin\
pause
+101
View File
@@ -0,0 +1,101 @@
#!/usr/bin/env bash
# SPDX-FileCopyrightText: 2026 citron Emulator Project
# SPDX-License-Identifier: GPL-3.0-or-later
#
# Automated MinGW (MSYS2 UCRT64) build script for Citron Neo.
# Run from the MSYS2 UCRT64 shell, or use build-for-mingw.bat to launch it.
set -euo pipefail
# ---------- configuration ----------
BUILD_DIR="${1:-build-mingw}"
BUILD_TYPE="${2:-Release}"
JOBS="${3:-$(nproc)}"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SOURCE_DIR="$SCRIPT_DIR"
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m'
info() { echo -e "${CYAN}[INFO]${NC} $*"; }
ok() { echo -e "${GREEN}[ OK ]${NC} $*"; }
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
fail() { echo -e "${RED}[FAIL]${NC} $*"; exit 1; }
# ---------- environment check ----------
if [[ -z "${MSYSTEM:-}" ]]; then
fail "Not running inside an MSYS2 shell. Use the UCRT64 terminal or build-for-mingw.bat."
fi
if [[ "$MSYSTEM" != "UCRT64" ]]; then
warn "MSYSTEM is '$MSYSTEM', expected 'UCRT64'. Builds may use the wrong toolchain."
fi
command -v pacman >/dev/null 2>&1 || fail "pacman not found — is this really MSYS2?"
# ---------- dependency installation ----------
info "Installing / updating build tools and dependencies via pacman …"
PACKAGES=(
# build tools
mingw-w64-ucrt-x86_64-cmake
mingw-w64-ucrt-x86_64-ninja
mingw-w64-ucrt-x86_64-toolchain
# required libraries
mingw-w64-ucrt-x86_64-boost
mingw-w64-ucrt-x86_64-fmt
mingw-w64-ucrt-x86_64-nlohmann-json
mingw-w64-ucrt-x86_64-opus
mingw-w64-ucrt-x86_64-SDL2
mingw-w64-ucrt-x86_64-qt6-base
mingw-w64-ucrt-x86_64-qt6-multimedia
mingw-w64-ucrt-x86_64-qt6-svg
mingw-w64-ucrt-x86_64-qt6-tools
mingw-w64-ucrt-x86_64-ffmpeg
mingw-w64-ucrt-x86_64-openal
mingw-w64-ucrt-x86_64-vulkan-headers
mingw-w64-ucrt-x86_64-vulkan-utility-libraries
mingw-w64-ucrt-x86_64-vulkan-memory-allocator
mingw-w64-ucrt-x86_64-libusb
mingw-w64-ucrt-x86_64-enet
mingw-w64-ucrt-x86_64-stb
)
pacman -S --needed --noconfirm "${PACKAGES[@]}" || fail "pacman install failed"
ok "All dependencies installed."
# ---------- configure ----------
info "Configuring CMake (${BUILD_TYPE}) in ${BUILD_DIR}"
mkdir -p "${SOURCE_DIR}/${BUILD_DIR}"
cd "${SOURCE_DIR}/${BUILD_DIR}"
cmake "$SOURCE_DIR" -G Ninja \
-DCMAKE_BUILD_TYPE="$BUILD_TYPE" \
-DCITRON_USE_BUNDLED_VCPKG=OFF \
-DCITRON_USE_BUNDLED_SDL2=OFF \
-DCITRON_USE_BUNDLED_QT=OFF \
-DCITRON_USE_BUNDLED_FFMPEG=OFF \
-DCITRON_USE_EXTERNAL_VULKAN_HEADERS=OFF \
-DCITRON_USE_EXTERNAL_VULKAN_UTILITY_LIBRARIES=OFF \
-DENABLE_QT_TRANSLATION=OFF \
-DUSE_DISCORD_PRESENCE=OFF \
-DCITRON_TESTS=OFF \
-DENABLE_WEB_SERVICE=OFF \
-DCITRON_USE_FASTER_LD=OFF \
|| fail "CMake configuration failed"
ok "CMake configuration succeeded."
# ---------- build ----------
info "Building with ${JOBS} parallel jobs …"
ninja -j"$JOBS" || fail "Build failed"
ok "Build complete!"
echo ""
info "Executables are in: ${SOURCE_DIR}/${BUILD_DIR}/bin/"
ls -lh "${SOURCE_DIR}/${BUILD_DIR}/bin/"*.exe 2>/dev/null || true
echo ""
ok "Done. You can run citron from: ${BUILD_DIR}/bin/citron.exe"
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

+1 -10
View File
@@ -20,16 +20,7 @@ if (MINGW OR (${CMAKE_SYSTEM_NAME} MATCHES "Linux") OR APPLE)
endif()
set(LIBUSB_PREFIX "${CMAKE_CURRENT_BINARY_DIR}/libusb")
# Source comes exclusively from CPM (libusb_src declared in dependencies.cmake).
# LIBUSB_CPM_SOURCE_DIR is the CACHE INTERNAL bridge set there; libusb_src_SOURCE_DIR
# is CPM's own auto-set and is always present even on a cache hit where _ADDED is false.
if (DEFINED LIBUSB_CPM_SOURCE_DIR)
set(LIBUSB_SRC_DIR "${LIBUSB_CPM_SOURCE_DIR}")
elseif (DEFINED libusb_src_SOURCE_DIR)
set(LIBUSB_SRC_DIR "${libusb_src_SOURCE_DIR}")
else()
set(LIBUSB_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/libusb")
endif()
set(LIBUSB_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/libusb")
# Workarounds for MSYS/MinGW
if (MSYS)
+174
View File
@@ -0,0 +1,174 @@
# SPDX-FileCopyrightText: 2025 citron Emulator Project
# SPDX-License-Identifier: GPL-2.0-or-later
# Fix PGO DLL Script for Citron (Windows)
# This script helps locate and copy the required pgort140.dll for MSVC PGO builds
param(
[Parameter()]
[string]$OutputDir = "build\bin\Release",
[Parameter()]
[switch]$Help
)
function Show-Usage {
Write-Host @"
Usage: .\fix-pgo-dll.ps1 [OPTIONS]
This script helps fix the "pgort140.DLL was not found" error by locating
and copying the required PGO runtime DLL to your build output directory.
Options:
-OutputDir PATH Target directory to copy DLL (default: build\bin\Release)
-Help Show this help message
Example:
.\fix-pgo-dll.ps1
.\fix-pgo-dll.ps1 -OutputDir "C:\MyBuild\bin"
"@
}
function Write-Header {
param([string]$Message)
Write-Host "`n=================================================================" -ForegroundColor Cyan
Write-Host $Message -ForegroundColor Cyan
Write-Host "=================================================================`n" -ForegroundColor Cyan
}
function Write-Info {
param([string]$Message)
Write-Host "[INFO] $Message" -ForegroundColor Green
}
function Write-Warning {
param([string]$Message)
Write-Host "[WARNING] $Message" -ForegroundColor Yellow
}
function Write-Error-Custom {
param([string]$Message)
Write-Host "[ERROR] $Message" -ForegroundColor Red
}
if ($Help) {
Show-Usage
exit 0
}
Write-Header "PGO DLL Fixer for Citron"
# Find Visual Studio installation
$VSInstallPath = $null
$VSWhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe"
if (Test-Path $VSWhere) {
Write-Info "Searching for Visual Studio installations..."
$VSInstallPath = & $VSWhere -latest -property installationPath
if ($VSInstallPath) {
Write-Info "Found Visual Studio at: $VSInstallPath"
}
} else {
Write-Warning "vswhere.exe not found. Trying common installation paths..."
}
# Common Visual Studio installation paths
$CommonPaths = @(
"${env:ProgramFiles}\Microsoft Visual Studio\2022\Community",
"${env:ProgramFiles}\Microsoft Visual Studio\2022\Professional",
"${env:ProgramFiles}\Microsoft Visual Studio\2022\Enterprise",
"${env:ProgramFiles(x86)}\Microsoft Visual Studio\2019\Community",
"${env:ProgramFiles(x86)}\Microsoft Visual Studio\2019\Professional",
"${env:ProgramFiles(x86)}\Microsoft Visual Studio\2019\Enterprise"
)
if (-not $VSInstallPath) {
foreach ($path in $CommonPaths) {
if (Test-Path $path) {
$VSInstallPath = $path
Write-Info "Found Visual Studio at: $VSInstallPath"
break
}
}
}
if (-not $VSInstallPath) {
Write-Error-Custom "Visual Studio installation not found!"
Write-Host "Please ensure Visual Studio 2019 or 2022 is installed with C++ support."
Write-Host "You can download it from: https://visualstudio.microsoft.com/downloads/"
exit 1
}
# Search for pgort140.dll
Write-Info "Searching for pgort140.dll..."
$DllPaths = @(
"$VSInstallPath\VC\Redist\MSVC\*\x64\Microsoft.VC*.CRT\pgort140.dll",
"$VSInstallPath\VC\Redist\MSVC\*\x86\Microsoft.VC*.CRT\pgort140.dll",
"$VSInstallPath\VC\Tools\MSVC\*\bin\Hostx64\x64\pgort140.dll",
"$VSInstallPath\VC\Tools\MSVC\*\bin\Hostx64\x86\pgort140.dll",
"$VSInstallPath\VC\Tools\MSVC\*\bin\Hostx86\x64\pgort140.dll",
"$VSInstallPath\VC\Tools\MSVC\*\bin\Hostx86\x86\pgort140.dll"
)
$FoundDll = $null
foreach ($pattern in $DllPaths) {
$matches = Get-ChildItem -Path $pattern -ErrorAction SilentlyContinue
if ($matches) {
$FoundDll = $matches[0].FullName
Write-Info "Found pgort140.dll at: $FoundDll"
break
}
}
if (-not $FoundDll) {
Write-Error-Custom "pgort140.dll not found in Visual Studio installation!"
Write-Host "This usually means:"
Write-Host "1. Visual Studio was installed without PGO support"
Write-Host "2. You need to install the 'MSVC v143 - VS 2022 C++ x64/x86 build tools' component"
Write-Host "3. Try repairing your Visual Studio installation"
Write-Host ""
Write-Host "To fix this:"
Write-Host "1. Open Visual Studio Installer"
Write-Host "2. Click 'Modify' on your Visual Studio installation"
Write-Host "3. Go to 'Individual components' tab"
Write-Host "4. Search for 'PGO' and ensure it's checked"
Write-Host "5. Click 'Modify' to install the component"
exit 1
}
# Create output directory if it doesn't exist
if (-not (Test-Path $OutputDir)) {
Write-Info "Creating output directory: $OutputDir"
New-Item -ItemType Directory -Force -Path $OutputDir | Out-Null
}
# Copy the DLL
try {
Write-Info "Copying pgort140.dll to: $OutputDir"
Copy-Item -Path $FoundDll -Destination $OutputDir -Force
Write-Info "Successfully copied pgort140.dll!"
# Verify the copy
$CopiedDll = Join-Path $OutputDir "pgort140.dll"
if (Test-Path $CopiedDll) {
Write-Info "Verification: pgort140.dll is now available in $OutputDir"
Write-Host ""
Write-Host "You can now run your PGO instrumented Citron build!"
} else {
Write-Error-Custom "Failed to copy pgort140.dll"
exit 1
}
} catch {
Write-Error-Custom "Error copying pgort140.dll: $($_.Exception.Message)"
exit 1
}
Write-Header "PGO DLL Fix Complete!"
Write-Info "The pgort140.dll has been copied to your build output directory."
Write-Info "Your PGO instrumented Citron build should now run without the DLL error."
Write-Host ""
Write-Info "Next steps:"
Write-Host "1. Run your PGO instrumented build"
Write-Host "2. Play games to collect profile data"
Write-Host "3. Rebuild with PGO USE stage for optimization"
+344
View File
@@ -0,0 +1,344 @@
# SPDX-FileCopyrightText: 2025 citron Emulator Project
# SPDX-License-Identifier: GPL-2.0-or-later
# PGO Build Script for Citron (Windows/PowerShell)
# This script automates the Profile-Guided Optimization build process
param(
[Parameter(Position=0, Mandatory=$true)]
[ValidateSet('generate', 'use', 'clean', 'merge', 'summary')]
[string]$Stage,
[Parameter()]
[int]$Jobs = 0,
[Parameter()]
[switch]$EnableLTO,
[Parameter()]
[switch]$Help
)
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
$BuildDir = Join-Path $ScriptDir "build"
$PgoProfilesDir = Join-Path $BuildDir "pgo-profiles"
$BackupProfilesDir = Join-Path $ScriptDir "pgo-profiles-backup"
function Show-Usage {
Write-Host @"
Usage: .\pgo-build.ps1 [STAGE] [OPTIONS]
STAGE can be:
generate - Build with PGO instrumentation (Stage 1)
use - Build using PGO profile data (Stage 2)
clean - Clean build directory but preserve profiles
merge - Merge PGC files into PGD without rebuilding
summary - Show profile coverage statistics
Example workflow:
.\pgo-build.ps1 generate # Build instrumented version
# Run citron.exe, play 2-3 games for 5-10 min each, exit cleanly
.\pgo-build.ps1 merge # Merge collected profiles
.\pgo-build.ps1 summary # Check coverage
.\pgo-build.ps1 use # Build optimized version
Options:
-Jobs N Number of parallel jobs (default: auto-detect)
-EnableLTO Enable Link-Time Optimization
-Help Show this help message
"@
}
function Write-Header {
param([string]$Message)
Write-Host "`n=================================================================" -ForegroundColor Cyan
Write-Host $Message -ForegroundColor Cyan
Write-Host "=================================================================`n" -ForegroundColor Cyan
}
function Write-Info {
param([string]$Message)
Write-Host "[INFO] $Message" -ForegroundColor Green
}
function Write-Warning {
param([string]$Message)
Write-Host "[WARNING] $Message" -ForegroundColor Yellow
}
function Write-Error-Custom {
param([string]$Message)
Write-Host "[ERROR] $Message" -ForegroundColor Red
}
function Find-Pgomgr {
$vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe"
if (Test-Path $vswhere) {
$vsPath = & $vswhere -latest -property installationPath
$pgomgr = Get-ChildItem -Path "$vsPath\VC\Tools\MSVC" -Recurse -Filter "pgomgr.exe" -ErrorAction SilentlyContinue |
Where-Object { $_.FullName -match "Hostx64\\x64" } |
Select-Object -First 1
if ($pgomgr) { return $pgomgr.FullName }
}
$found = Get-Command pgomgr.exe -ErrorAction SilentlyContinue
if ($found) { return $found.Source }
return $null
}
function Clean-StaleProfiles {
if (-not (Test-Path $PgoProfilesDir)) { return }
$pgcFiles = Get-ChildItem -Path $PgoProfilesDir -Filter "*.pgc" -ErrorAction SilentlyContinue
if ($pgcFiles -and $pgcFiles.Count -gt 0) {
Write-Info "Removing $($pgcFiles.Count) stale PGC file(s)..."
$pgcFiles | Remove-Item -Force
}
}
function Merge-Profiles {
$pgomgr = Find-Pgomgr
if (-not $pgomgr) {
Write-Warning "pgomgr.exe not found. Cannot merge profiles."
Write-Info "Ensure Visual Studio Build Tools are installed and on PATH."
return $false
}
$pgdFiles = Get-ChildItem -Path $PgoProfilesDir -Filter "*.pgd" -ErrorAction SilentlyContinue
if (-not $pgdFiles -or $pgdFiles.Count -eq 0) {
Write-Warning "No PGD files found in $PgoProfilesDir"
return $false
}
$pgcFiles = Get-ChildItem -Path $PgoProfilesDir -Filter "*.pgc" -ErrorAction SilentlyContinue
if (-not $pgcFiles -or $pgcFiles.Count -eq 0) {
Write-Info "No PGC files to merge."
return $true
}
foreach ($pgd in $pgdFiles) {
Write-Info "Merging PGC files into $($pgd.Name)..."
& $pgomgr /merge $pgd.FullName
if ($LASTEXITCODE -ne 0) {
Write-Warning "pgomgr /merge returned exit code $LASTEXITCODE for $($pgd.Name)"
}
}
# Clean PGC files after successful merge
Write-Info "Cleaning merged PGC files..."
Get-ChildItem -Path $PgoProfilesDir -Filter "*.pgc" -ErrorAction SilentlyContinue | Remove-Item -Force
return $true
}
function Show-ProfileSummary {
$pgomgr = Find-Pgomgr
if (-not $pgomgr) {
Write-Warning "pgomgr.exe not found. Cannot show summary."
return
}
$pgdFiles = Get-ChildItem -Path $PgoProfilesDir -Filter "*.pgd" -ErrorAction SilentlyContinue
if (-not $pgdFiles -or $pgdFiles.Count -eq 0) {
Write-Warning "No PGD files found in $PgoProfilesDir"
return
}
foreach ($pgd in $pgdFiles) {
Write-Header "Profile Summary: $($pgd.Name)"
& $pgomgr /summary $pgd.FullName
Write-Host ""
}
}
if ($Help) {
Show-Usage
exit 0
}
# Auto-detect number of processors
if ($Jobs -eq 0) {
$Jobs = $env:NUMBER_OF_PROCESSORS
if (-not $Jobs) { $Jobs = 4 }
}
$LtoFlag = if ($EnableLTO) { "ON" } else { "OFF" }
# --- Clean stage ---
if ($Stage -eq "clean") {
Write-Header "Cleaning Build Directory"
if (Test-Path $PgoProfilesDir) {
Write-Info "Backing up PGO profiles..."
New-Item -ItemType Directory -Force -Path $BackupProfilesDir | Out-Null
Copy-Item -Path "$PgoProfilesDir\*" -Destination $BackupProfilesDir -Recurse -Force -ErrorAction SilentlyContinue
}
if (Test-Path $BuildDir) {
Write-Info "Removing build directory..."
Remove-Item -Path $BuildDir -Recurse -Force
}
if (Test-Path $BackupProfilesDir) {
Write-Info "Restoring PGO profiles..."
New-Item -ItemType Directory -Force -Path $PgoProfilesDir | Out-Null
Move-Item -Path "$BackupProfilesDir\*" -Destination $PgoProfilesDir -Force -ErrorAction SilentlyContinue
Remove-Item -Path $BackupProfilesDir -Recurse -Force
}
Write-Info "Clean complete!"
exit 0
}
# --- Merge stage ---
if ($Stage -eq "merge") {
Write-Header "Merging PGO Profiles"
if (-not (Test-Path $PgoProfilesDir)) {
Write-Error-Custom "Profile directory not found: $PgoProfilesDir"
exit 1
}
$result = Merge-Profiles
if ($result) {
Write-Info "Merge complete! Run '.\pgo-build.ps1 summary' to check coverage."
} else {
Write-Error-Custom "Merge failed."
exit 1
}
exit 0
}
# --- Summary stage ---
if ($Stage -eq "summary") {
Write-Header "PGO Profile Summary"
if (-not (Test-Path $PgoProfilesDir)) {
Write-Error-Custom "Profile directory not found: $PgoProfilesDir"
exit 1
}
Show-ProfileSummary
exit 0
}
# --- Generate stage ---
if ($Stage -eq "generate") {
Write-Header "PGO Stage 1: Generate Profile Data"
# Clean stale PGC files from any previous run
if (Test-Path $PgoProfilesDir) {
Clean-StaleProfiles
}
# Create build directory
New-Item -ItemType Directory -Force -Path $BuildDir | Out-Null
Set-Location $BuildDir
# Configure
Write-Info "Configuring CMake..."
cmake .. `
-DCITRON_ENABLE_PGO_GENERATE=ON `
-DCITRON_ENABLE_LTO=$LtoFlag `
-DCMAKE_BUILD_TYPE=Release
if ($LASTEXITCODE -ne 0) {
Write-Error-Custom "CMake configuration failed"
exit 1
}
# Build
Write-Info "Building instrumented Citron (this may take a while)..."
cmake --build . --config Release -j $Jobs
if ($LASTEXITCODE -ne 0) {
Write-Error-Custom "Build failed"
exit 1
}
Write-Header "Build Complete!"
Write-Host ""
Write-Host " Training guide for best PGO results:" -ForegroundColor Yellow
Write-Host ""
Write-Host " 1. Run: .\bin\Release\citron.exe"
Write-Host " 2. Launch a game and play PAST initial loading"
Write-Host " (first-time shader compilation is a critical hot path)"
Write-Host " 3. Play for at least 5-10 minutes per game"
Write-Host " 4. Test 2-3 different games for broader code coverage"
Write-Host " 5. Navigate menus, settings, and game list to profile the UI"
Write-Host " 6. Exit citron cleanly (File -> Exit or Ctrl+Q)"
Write-Host ""
Write-Host " After each session, you can run:" -ForegroundColor Yellow
Write-Host " .\pgo-build.ps1 merge # Consolidate collected profiles"
Write-Host " .\pgo-build.ps1 summary # Check profile coverage"
Write-Host ""
Write-Host " When satisfied with coverage, build the optimized binary:" -ForegroundColor Yellow
Write-Host " .\pgo-build.ps1 use"
Write-Host ""
Set-Location $ScriptDir
}
# --- Use stage ---
if ($Stage -eq "use") {
Write-Header "PGO Stage 2: Build Optimized Binary"
# Check if profile data exists
if (-not (Test-Path $PgoProfilesDir) -or -not (Get-ChildItem $PgoProfilesDir -ErrorAction SilentlyContinue)) {
Write-Error-Custom "No profile data found in $PgoProfilesDir"
Write-Info "Please run the generate stage first and collect profile data"
exit 1
}
# Merge any outstanding PGC files before building
Write-Info "Merging any outstanding PGC files..."
Merge-Profiles | Out-Null
# Backup profiles if build directory exists
if (Test-Path $BuildDir) {
Write-Info "Backing up PGO profiles..."
New-Item -ItemType Directory -Force -Path $BackupProfilesDir | Out-Null
Copy-Item -Path "$PgoProfilesDir\*" -Destination $BackupProfilesDir -Recurse -Force
Remove-Item -Path $BuildDir -Recurse -Force
}
# Create build directory and restore profiles
New-Item -ItemType Directory -Force -Path $BuildDir | Out-Null
if (Test-Path $BackupProfilesDir) {
New-Item -ItemType Directory -Force -Path $PgoProfilesDir | Out-Null
Move-Item -Path "$BackupProfilesDir\*" -Destination $PgoProfilesDir -Force
Remove-Item -Path $BackupProfilesDir -Recurse -Force
}
Set-Location $BuildDir
# Configure
Write-Info "Configuring CMake..."
cmake .. `
-DCITRON_ENABLE_PGO_USE=ON `
-DCITRON_ENABLE_LTO=$LtoFlag `
-DCMAKE_BUILD_TYPE=Release
if ($LASTEXITCODE -ne 0) {
Write-Error-Custom "CMake configuration failed"
Set-Location $ScriptDir
exit 1
}
# Build
Write-Info "Building optimized Citron (this may take a while)..."
cmake --build . --config Release -j $Jobs
if ($LASTEXITCODE -ne 0) {
Write-Error-Custom "Build failed"
Set-Location $ScriptDir
exit 1
}
Write-Header "Build Complete!"
Write-Info "Your optimized Citron binary is ready!"
Write-Info "Location: $BuildDir\bin\Release\citron.exe"
Write-Host ""
Write-Info "This build is optimized for your specific usage patterns."
Set-Location $ScriptDir
}
+328
View File
@@ -0,0 +1,328 @@
#!/bin/bash
# SPDX-FileCopyrightText: 2025 citron Emulator Project
# SPDX-License-Identifier: GPL-2.0-or-later
# PGO Build Script for Citron (Linux/macOS)
# This script automates the Profile-Guided Optimization build process
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BUILD_DIR="${SCRIPT_DIR}/build"
PGO_PROFILES_DIR="${BUILD_DIR}/pgo-profiles"
BACKUP_PROFILES_DIR="${SCRIPT_DIR}/pgo-profiles-backup"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
print_header() {
echo -e "${BLUE}=================================================================${NC}"
echo -e "${BLUE}$1${NC}"
echo -e "${BLUE}=================================================================${NC}"
}
print_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
show_usage() {
echo "Usage: $0 [STAGE] [OPTIONS]"
echo ""
echo "STAGE can be:"
echo " generate - Build with PGO instrumentation (Stage 1)"
echo " use - Build using PGO profile data (Stage 2)"
echo " clean - Clean build directory but preserve profiles"
echo " merge - Merge profile data (Clang: .profraw -> .profdata)"
echo " summary - Show profile file statistics"
echo ""
echo "Example workflow:"
echo " $0 generate # Build instrumented version"
echo " # Run citron, play 2-3 games for 5-10 min each, exit cleanly"
echo " $0 merge # Merge collected profiles"
echo " $0 summary # Check coverage"
echo " $0 use # Build optimized version"
echo ""
echo "Options:"
echo " -j N Number of parallel jobs (default: auto-detect)"
echo " -lto Enable Link-Time Optimization"
echo " -h Show this help message"
}
clean_stale_profiles() {
if [ ! -d "$PGO_PROFILES_DIR" ]; then
return
fi
local gcda_count
gcda_count=$(find "$PGO_PROFILES_DIR" -name "*.gcda" 2>/dev/null | wc -l)
if [ "$gcda_count" -gt 0 ]; then
print_info "Removing $gcda_count stale .gcda file(s)..."
find "$PGO_PROFILES_DIR" -name "*.gcda" -delete
fi
local profraw_count
profraw_count=$(find "$PGO_PROFILES_DIR" -name "*.profraw" 2>/dev/null | wc -l)
if [ "$profraw_count" -gt 0 ]; then
print_info "Removing $profraw_count stale .profraw file(s)..."
find "$PGO_PROFILES_DIR" -name "*.profraw" -delete
fi
}
merge_clang_profiles() {
if ! command -v llvm-profdata &>/dev/null; then
print_warning "llvm-profdata not found. Cannot merge Clang profiles."
return 1
fi
local profraw_files
profraw_files=$(find "$PGO_PROFILES_DIR" -name "*.profraw" 2>/dev/null)
if [ -z "$profraw_files" ]; then
print_info "No .profraw files to merge."
return 0
fi
local count
count=$(echo "$profraw_files" | wc -l)
print_info "Merging $count .profraw file(s)..."
llvm-profdata merge \
-output="${PGO_PROFILES_DIR}/default.profdata" \
${profraw_files}
print_info "Merged into ${PGO_PROFILES_DIR}/default.profdata"
# Clean raw files after merge
print_info "Cleaning merged .profraw files..."
find "$PGO_PROFILES_DIR" -name "*.profraw" -delete
return 0
}
show_profile_summary() {
if [ ! -d "$PGO_PROFILES_DIR" ]; then
print_warning "Profile directory not found: $PGO_PROFILES_DIR"
return
fi
print_header "Profile Summary"
local profdata="${PGO_PROFILES_DIR}/default.profdata"
if [ -f "$profdata" ]; then
local size
size=$(du -h "$profdata" | cut -f1)
print_info "Clang profile: $profdata ($size)"
if command -v llvm-profdata &>/dev/null; then
llvm-profdata show --counts --all-functions "$profdata" 2>/dev/null | tail -5
fi
fi
local gcda_count
gcda_count=$(find "$PGO_PROFILES_DIR" -name "*.gcda" 2>/dev/null | wc -l)
if [ "$gcda_count" -gt 0 ]; then
print_info "GCC profile files: $gcda_count .gcda files"
local total_size
total_size=$(find "$PGO_PROFILES_DIR" -name "*.gcda" -exec du -ch {} + 2>/dev/null | tail -1 | cut -f1)
print_info "Total .gcda size: $total_size"
fi
local profraw_count
profraw_count=$(find "$PGO_PROFILES_DIR" -name "*.profraw" 2>/dev/null | wc -l)
if [ "$profraw_count" -gt 0 ]; then
print_warning "$profraw_count unmerged .profraw file(s) found. Run '$0 merge' first."
fi
if [ "$gcda_count" -eq 0 ] && [ ! -f "$profdata" ] && [ "$profraw_count" -eq 0 ]; then
print_warning "No profile data found in $PGO_PROFILES_DIR"
fi
}
# Parse arguments
STAGE=""
JOBS=$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo "4")
ENABLE_LTO="OFF"
while [[ $# -gt 0 ]]; do
case $1 in
generate|use|clean|merge|summary)
STAGE="$1"
shift
;;
-j)
JOBS="$2"
shift 2
;;
-lto)
ENABLE_LTO="ON"
shift
;;
-h|--help)
show_usage
exit 0
;;
*)
print_error "Unknown option: $1"
show_usage
exit 1
;;
esac
done
if [ -z "$STAGE" ]; then
print_error "No stage specified"
show_usage
exit 1
fi
# --- Clean stage ---
if [ "$STAGE" == "clean" ]; then
print_header "Cleaning Build Directory"
if [ -d "$PGO_PROFILES_DIR" ]; then
print_info "Backing up PGO profiles..."
mkdir -p "$BACKUP_PROFILES_DIR"
cp -r "$PGO_PROFILES_DIR"/* "$BACKUP_PROFILES_DIR/" 2>/dev/null || true
fi
if [ -d "$BUILD_DIR" ]; then
print_info "Removing build directory..."
rm -rf "$BUILD_DIR"
fi
if [ -d "$BACKUP_PROFILES_DIR" ]; then
print_info "Restoring PGO profiles..."
mkdir -p "$PGO_PROFILES_DIR"
mv "$BACKUP_PROFILES_DIR"/* "$PGO_PROFILES_DIR/" 2>/dev/null || true
rm -rf "$BACKUP_PROFILES_DIR"
fi
print_info "Clean complete!"
exit 0
fi
# --- Merge stage ---
if [ "$STAGE" == "merge" ]; then
print_header "Merging PGO Profiles"
if [ ! -d "$PGO_PROFILES_DIR" ]; then
print_error "Profile directory not found: $PGO_PROFILES_DIR"
exit 1
fi
merge_clang_profiles
print_info "Done! Run '$0 summary' to check coverage."
exit 0
fi
# --- Summary stage ---
if [ "$STAGE" == "summary" ]; then
show_profile_summary
exit 0
fi
# --- Generate stage ---
if [ "$STAGE" == "generate" ]; then
print_header "PGO Stage 1: Generate Profile Data"
# Clean stale profile data from any previous run
if [ -d "$PGO_PROFILES_DIR" ]; then
clean_stale_profiles
fi
# Create build directory
mkdir -p "$BUILD_DIR"
cd "$BUILD_DIR"
# Configure
print_info "Configuring CMake..."
cmake .. \
-DCITRON_ENABLE_PGO_GENERATE=ON \
-DCITRON_ENABLE_LTO=$ENABLE_LTO \
-DCMAKE_BUILD_TYPE=Release
# Build
print_info "Building instrumented Citron (this may take a while)..."
cmake --build . -j"$JOBS"
print_header "Build Complete!"
echo ""
echo -e "${YELLOW} Training guide for best PGO results:${NC}"
echo ""
echo " 1. Run: ./bin/citron"
echo " 2. Launch a game and play PAST initial loading"
echo " (first-time shader compilation is a critical hot path)"
echo " 3. Play for at least 5-10 minutes per game"
echo " 4. Test 2-3 different games for broader code coverage"
echo " 5. Navigate menus, settings, and game list to profile the UI"
echo " 6. Exit citron cleanly (File -> Exit or Ctrl+Q)"
echo ""
echo -e "${YELLOW} After each session, you can run:${NC}"
echo " $0 merge # Consolidate collected profiles"
echo " $0 summary # Check profile coverage"
echo ""
echo -e "${YELLOW} When satisfied with coverage, build the optimized binary:${NC}"
echo " $0 use"
echo ""
fi
# --- Use stage ---
if [ "$STAGE" == "use" ]; then
print_header "PGO Stage 2: Build Optimized Binary"
# Check if profile data exists
if [ ! -d "$PGO_PROFILES_DIR" ] || [ -z "$(ls -A $PGO_PROFILES_DIR 2>/dev/null)" ]; then
print_error "No profile data found in $PGO_PROFILES_DIR"
print_info "Please run the generate stage first and collect profile data"
exit 1
fi
# Merge any outstanding raw profiles before building
print_info "Merging any outstanding profile data..."
merge_clang_profiles || true
# Backup profiles if build directory exists
if [ -d "$BUILD_DIR" ]; then
print_info "Backing up PGO profiles..."
mkdir -p "$BACKUP_PROFILES_DIR"
cp -r "$PGO_PROFILES_DIR"/* "$BACKUP_PROFILES_DIR/"
rm -rf "$BUILD_DIR"
fi
# Create build directory and restore profiles
mkdir -p "$BUILD_DIR"
if [ -d "$BACKUP_PROFILES_DIR" ]; then
mkdir -p "$PGO_PROFILES_DIR"
mv "$BACKUP_PROFILES_DIR"/* "$PGO_PROFILES_DIR/"
rm -rf "$BACKUP_PROFILES_DIR"
fi
cd "$BUILD_DIR"
# Configure
print_info "Configuring CMake..."
cmake .. \
-DCITRON_ENABLE_PGO_USE=ON \
-DCITRON_ENABLE_LTO=$ENABLE_LTO \
-DCMAKE_BUILD_TYPE=Release
# Build
print_info "Building optimized Citron (this may take a while)..."
cmake --build . -j"$JOBS"
print_header "Build Complete!"
print_info "Your optimized Citron binary is ready!"
print_info "Location: $BUILD_DIR/bin/citron"
echo ""
print_info "This build is optimized for your specific usage patterns."
fi