Add a riscv64 cross build, generalizing the loongarch64 one

The pieces were nearly all in place already - CMakeLists has detected
riscv64 and set RISCV64 since the backend landed - so this is mostly a
matter of not hardcoding loongarch64 in the cross-build plumbing:

- setup-loongarch64-cross.sh becomes setup-cross.sh <target>, taking the
  triple and dynamic linker name from the argument.
- b.sh gains --riscv64, and the GL stub block is keyed off a compiler
  variable rather than a loongarch64-only flag.
- New cmake/Toolchains/riscv64-linux-gnu.cmake, mirroring the existing one.

The SDL carve-out moves from LOONGARCH64_DEVICE to a new HEADLESS_CROSS
option set by b.sh. That flag was keyed off the target architecture, which
is also true when building natively on such a machine - harmless so far,
but riscv64 hardware that people actually build PPSSPP on exists, and it
should still get the normal SDL frontend.

Both toolchain files now find qemu rather than assuming the static build:
Debian ships the static binaries in qemu-user-static and the dynamic ones
in qemu-user, and only the latter is available on some releases. Neither
being present is fine too - it just means no compile-check programs run.

Verified by a clean loongarch64 build; riscv64 is untested so far, the
toolchain isn't installed here yet.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
Henrik RydgårdandClaude Opus 5 committed 2026-09-19 11:52:16 -06:00
1 parent ba06b454f3
commit 9a29bf84d8
6 files changed
+129 -46

No files matched your search

+12 -5
View File
@@ -279,6 +279,13 @@ jobs:
args: ./b.sh --loongarch64 PPSSPPHeadless args: ./b.sh --loongarch64 PPSSPPHeadless
id: loongarch64 id: loongarch64
- os: ubuntu-26.04
extra: riscv64
cc: gcc
cxx: g++
args: ./b.sh --riscv64 PPSSPPHeadless
id: riscv64
- os: macos-latest - os: macos-latest
extra: test extra: test
cc: clang cc: clang
@@ -318,7 +325,7 @@ jobs:
ndk-version: r29 ndk-version: r29
- name: Install Linux dependencies - name: Install Linux dependencies
if: runner.os == 'Linux' && matrix.extra != 'android' && matrix.extra != 'loongarch64' if: runner.os == 'Linux' && matrix.extra != 'android' && matrix.extra != 'loongarch64' && matrix.extra != 'riscv64'
run: | run: |
sudo apt-get update -y -qq sudo apt-get update -y -qq
sudo apt-get install libsdl3-dev libgl1-mesa-dev libglu1-mesa-dev libsdl3-ttf-dev libfontconfig1-dev libcurl4-openssl-dev sudo apt-get install libsdl3-dev libgl1-mesa-dev libglu1-mesa-dev libsdl3-ttf-dev libfontconfig1-dev libcurl4-openssl-dev
@@ -327,12 +334,12 @@ jobs:
if: runner.os == 'macOS' && matrix.id == 'macos' if: runner.os == 'macOS' && matrix.id == 'macos'
run: brew install sdl3 sdl3_ttf run: brew install sdl3 sdl3_ttf
- name: Install loongarch64 cross-compilation dependencies - name: Install cross-compilation dependencies
if: matrix.extra == 'loongarch64' if: matrix.extra == 'loongarch64' || matrix.extra == 'riscv64'
run: | run: |
sudo apt-get update -y -qq sudo apt-get update -y -qq
sudo apt-get install -y gcc-14-loongarch64-linux-gnu g++-14-loongarch64-linux-gnu binutils-loongarch64-linux-gnu libgl-dev libglu1-mesa-dev sudo apt-get install -y gcc-14-${{ matrix.extra }}-linux-gnu g++-14-${{ matrix.extra }}-linux-gnu binutils-${{ matrix.extra }}-linux-gnu libgl-dev libglu1-mesa-dev
sudo cmake/scripts/setup-loongarch64-cross.sh sudo cmake/scripts/setup-cross.sh ${{ matrix.extra }}
- name: Install iOS dependencies - name: Install iOS dependencies
if: matrix.id == 'ios' if: matrix.id == 'ios'
+8 -4
View File
@@ -184,6 +184,8 @@ option(USE_VULKAN_DISPLAY_KHR "Enable or disable full screen display of Vulkan"
# :: Frontends # :: Frontends
option(MOBILE_DEVICE "Set to ON when targeting a mobile device" ${MOBILE_DEVICE}) option(MOBILE_DEVICE "Set to ON when targeting a mobile device" ${MOBILE_DEVICE})
option(HEADLESS "Set to OFF to not generate the PPSSPPHeadless target" ${HEADLESS}) option(HEADLESS "Set to OFF to not generate the PPSSPPHeadless target" ${HEADLESS})
# Set by b.sh for the loongarch64/riscv64 cross builds, whose sysroots have no SDL3.
option(HEADLESS_CROSS "Set to ON for a headless-only cross build (no SDL frontend)" ${HEADLESS_CROSS})
option(ATLAS_TOOL "Set to OFF to not generate the ATLAS_TOOL target" ${ATLAS_TOOL}) option(ATLAS_TOOL "Set to OFF to not generate the ATLAS_TOOL target" ${ATLAS_TOOL})
option(UNITTEST "Set to ON to generate the unittest target" ${UNITTEST}) option(UNITTEST "Set to ON to generate the unittest target" ${UNITTEST})
option(SIMULATOR "Set to ON when targeting an x86 simulator of an ARM platform" ${SIMULATOR}) option(SIMULATOR "Set to ON when targeting an x86 simulator of an ARM platform" ${SIMULATOR})
@@ -331,7 +333,7 @@ set(SDL_LIB_TARGET "")
set(SDL_TTF_LIB_TARGET "") set(SDL_TTF_LIB_TARGET "")
set(FONTCONFIG_LIB_TARGET "") set(FONTCONFIG_LIB_TARGET "")
if(NOT LIBRETRO AND NOT IOS AND NOT LOONGARCH64_DEVICE AND NOT ANDROID) if(NOT LIBRETRO AND NOT IOS AND NOT HEADLESS_CROSS AND NOT ANDROID)
find_package(SDL3 QUIET) find_package(SDL3 QUIET)
if(NOT SDL3_FOUND) if(NOT SDL3_FOUND)
if(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND (EXISTS "/usr/bin/apt" OR EXISTS "/usr/bin/apt-get" OR EXISTS "/etc/debian_version")) if(CMAKE_SYSTEM_NAME STREQUAL "Linux" AND (EXISTS "/usr/bin/apt" OR EXISTS "/usr/bin/apt-get" OR EXISTS "/etc/debian_version"))
@@ -1001,9 +1003,11 @@ elseif(WIN32)
# Don't care about SDL. # Don't care about SDL.
set(TargetBin PPSSPPWindows) set(TargetBin PPSSPPWindows)
elseif(LIBRETRO) elseif(LIBRETRO)
elseif(LOONGARCH64_DEVICE) elseif(HEADLESS_CROSS)
# Cross-compiling for loongarch64 currently only builds PPSSPPHeadless, and there's # The loongarch64/riscv64 cross builds only build PPSSPPHeadless, and there's no
# no SDL3 available for that target, so skip the desktop SDL frontend entirely. # SDL3 available in those sysroots, so skip the desktop SDL frontend entirely.
# Note this is deliberately not keyed off the target architecture: a native build
# on such a machine can have SDL3 and should still get the normal frontend.
else() else()
if(GOLD) if(GOLD)
set(TargetBin PPSSPPGold) set(TargetBin PPSSPPGold)
+12 -7
View File
@@ -31,9 +31,14 @@ do
CMAKE_ARGS="-DCMAKE_TOOLCHAIN_FILE=cmake/Toolchains/raspberry.armv8.cmake ${CMAKE_ARGS}" CMAKE_ARGS="-DCMAKE_TOOLCHAIN_FILE=cmake/Toolchains/raspberry.armv8.cmake ${CMAKE_ARGS}"
;; ;;
--loongarch64) --loongarch64)
CMAKE_ARGS="-DCMAKE_TOOLCHAIN_FILE=cmake/Toolchains/loongarch64-linux-gnu.cmake -DHEADLESS=ON -DUSE_SYSTEM_LIBPNG=OFF -DUSE_SYSTEM_LIBSDL2=OFF ${CMAKE_ARGS}" CMAKE_ARGS="-DCMAKE_TOOLCHAIN_FILE=cmake/Toolchains/loongarch64-linux-gnu.cmake -DHEADLESS=ON -DHEADLESS_CROSS=ON -DUSE_SYSTEM_LIBPNG=OFF -DUSE_SYSTEM_LIBSDL2=OFF ${CMAKE_ARGS}"
TARGET_OS=loongarch64 TARGET_OS=loongarch64
LOONGARCH64_BUILD=1 CROSS_STUB_CC=loongarch64-linux-gnu-gcc-14
;;
--riscv64)
CMAKE_ARGS="-DCMAKE_TOOLCHAIN_FILE=cmake/Toolchains/riscv64-linux-gnu.cmake -DHEADLESS=ON -DHEADLESS_CROSS=ON -DUSE_SYSTEM_LIBPNG=OFF -DUSE_SYSTEM_LIBSDL2=OFF ${CMAKE_ARGS}"
TARGET_OS=riscv64
CROSS_STUB_CC=riscv64-linux-gnu-gcc-14
;; ;;
--android) CMAKE_ARGS="-DCMAKE_TOOLCHAIN_FILE=android/android.toolchain.cmake ${CMAKE_ARGS}" --android) CMAKE_ARGS="-DCMAKE_TOOLCHAIN_FILE=android/android.toolchain.cmake ${CMAKE_ARGS}"
TARGET_OS=Android TARGET_OS=Android
@@ -122,16 +127,16 @@ echo Building with $CORES_COUNT threads
mkdir -p ${BUILD_DIR} mkdir -p ${BUILD_DIR}
# For loongarch64 cross-compilation, build a comprehensive GL/GLX stub into # For the headless cross targets, build a comprehensive GL/GLX stub into
# <build>/stublibs/libGL.so so GLEW's static archive can resolve its symbols # <build>/stublibs/libGL.so so GLEW's static archive can resolve its symbols
# via PLT entries (a direct B26 branch to address 0 overflows on LoongArch). # via PLT entries (a direct branch to address 0 overflows the relocation).
# This stub is always (re)generated to pick up any new needed symbols. # This stub is always (re)generated to pick up any new needed symbols.
if [ ! -z "$LOONGARCH64_BUILD" ]; then if [ ! -z "$CROSS_STUB_CC" ]; then
STUB_DIR=${BUILD_DIR}/stublibs STUB_DIR=${BUILD_DIR}/stublibs
STUB_GL=${STUB_DIR}/libGL.so STUB_GL=${STUB_DIR}/libGL.so
mkdir -p "${STUB_DIR}" mkdir -p "${STUB_DIR}"
STUB_C=$(mktemp /tmp/gl_stub_XXXXXX.c) STUB_C=$(mktemp /tmp/gl_stub_XXXXXX.c)
echo "/* Loongarch64 GL/GLX stub - cross-compilation only */" > "$STUB_C" echo "/* ${TARGET_OS} GL/GLX stub - cross-compilation only */" > "$STUB_C"
# Collect all T (exported) symbols from GL/GLX libs, deduplicate, emit stubs # Collect all T (exported) symbols from GL/GLX libs, deduplicate, emit stubs
HOST_MULTIARCH=$(gcc -print-multiarch 2>/dev/null || dpkg-architecture -qDEB_HOST_MULTIARCH 2>/dev/null) HOST_MULTIARCH=$(gcc -print-multiarch 2>/dev/null || dpkg-architecture -qDEB_HOST_MULTIARCH 2>/dev/null)
{ {
@@ -144,7 +149,7 @@ if [ ! -z "$LOONGARCH64_BUILD" ]; then
printf '%s\n' glXGetProcAddressARB glXGetClientString glXQueryVersion \ printf '%s\n' glXGetProcAddressARB glXGetClientString glXQueryVersion \
glBindTexture glGetString glGetIntegerv glBindTexture glGetString glGetIntegerv
} | sort -u | awk '{ print "void "$1"(void){}" }' >> "$STUB_C" } | sort -u | awk '{ print "void "$1"(void){}" }' >> "$STUB_C"
loongarch64-linux-gnu-gcc-14 -shared -fPIC -Wno-implicit-function-declaration \ $CROSS_STUB_CC -shared -fPIC -Wno-implicit-function-declaration \
-o "${STUB_GL}" "$STUB_C" -o "${STUB_GL}" "$STUB_C"
rm "$STUB_C" rm "$STUB_C"
fi fi
+16 -12
View File
@@ -17,8 +17,8 @@ set(USING_X11_VULKAN OFF CACHE BOOL "" FORCE)
# Use legacy GL preference so find_package(OpenGL) looks for a single libGL.so # Use legacy GL preference so find_package(OpenGL) looks for a single libGL.so
# rather than the GLVND split (libOpenGL.so + libGLX.so), which we don't have. # rather than the GLVND split (libOpenGL.so + libGLX.so), which we don't have.
# A stub libGL.so + GL headers must be present in the sysroot; they are placed # A stub libGL.so + GL headers must be present in the sysroot; they are placed
# there by cmake/scripts/setup-loongarch64-cross.sh (run once with sudo, or # there by cmake/scripts/setup-cross.sh (run once with sudo, or automatically
# automatically by the CI install step). # by the CI install step).
set(OpenGL_GL_PREFERENCE LEGACY CACHE STRING "" FORCE) set(OpenGL_GL_PREFERENCE LEGACY CACHE STRING "" FORCE)
# b.sh also creates a stub in <build-dir>/stublibs/ as a local fallback so the # b.sh also creates a stub in <build-dir>/stublibs/ as a local fallback so the
@@ -27,14 +27,18 @@ set(CMAKE_EXE_LINKER_FLAGS
"${CMAKE_EXE_LINKER_FLAGS} -L${CMAKE_BINARY_DIR}/stublibs" "${CMAKE_EXE_LINKER_FLAGS} -L${CMAKE_BINARY_DIR}/stublibs"
CACHE STRING "" FORCE) CACHE STRING "" FORCE)
# Allow CMake's compile-check programs to run via QEMU. # Allow CMake's compile-check programs to run via QEMU, if it's installed.
# The /lib64 symlink is created by the setup script; without it, pass -L # Debian/Ubuntu ship the static binaries in qemu-user-static and the dynamic
# explicitly so QEMU can find the loongarch64 dynamic linker. # ones in qemu-user; either will do, so take whichever is present.
if(EXISTS "/lib64/ld-linux-loongarch-lp64d.so.1") find_program(QEMU_LOONGARCH64 NAMES qemu-loongarch64-static qemu-loongarch64)
set(CMAKE_CROSSCOMPILING_EMULATOR "qemu-loongarch64-static" if(QEMU_LOONGARCH64)
CACHE STRING "" FORCE) # The /lib64 symlink is created by the setup script; without it, pass -L
else() # explicitly so QEMU can find the loongarch64 dynamic linker.
set(CMAKE_CROSSCOMPILING_EMULATOR if(EXISTS "/lib64/ld-linux-loongarch-lp64d.so.1")
"qemu-loongarch64-static;-L;/usr/loongarch64-linux-gnu" set(CMAKE_CROSSCOMPILING_EMULATOR "${QEMU_LOONGARCH64}" CACHE STRING "" FORCE)
CACHE STRING "" FORCE) else()
set(CMAKE_CROSSCOMPILING_EMULATOR
"${QEMU_LOONGARCH64};-L;/usr/loongarch64-linux-gnu"
CACHE STRING "" FORCE)
endif()
endif() endif()
+44
View File
@@ -0,0 +1,44 @@
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR riscv64)
set(CMAKE_C_COMPILER riscv64-linux-gnu-gcc-14)
set(CMAKE_CXX_COMPILER riscv64-linux-gnu-g++-14)
set(CMAKE_ASM_COMPILER riscv64-linux-gnu-gcc-14)
set(CMAKE_FIND_ROOT_PATH /usr/riscv64-linux-gnu)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)
# No X11 in the sysroot; disable the X11 Vulkan path to avoid include-dir errors.
set(USING_X11_VULKAN OFF CACHE BOOL "" FORCE)
# Use legacy GL preference so find_package(OpenGL) looks for a single libGL.so
# rather than the GLVND split (libOpenGL.so + libGLX.so), which we don't have.
# A stub libGL.so + GL headers must be present in the sysroot; they are placed
# there by cmake/scripts/setup-cross.sh (run once with sudo, or automatically
# by the CI install step).
set(OpenGL_GL_PREFERENCE LEGACY CACHE STRING "" FORCE)
# b.sh also creates a stub in <build-dir>/stublibs/ as a local fallback so the
# linker can resolve GL/GLX calls from GLEW's static archive via PLT entries.
set(CMAKE_EXE_LINKER_FLAGS
"${CMAKE_EXE_LINKER_FLAGS} -L${CMAKE_BINARY_DIR}/stublibs"
CACHE STRING "" FORCE)
# Allow CMake's compile-check programs to run via QEMU, if it's installed.
# Debian/Ubuntu ship the static binaries in qemu-user-static and the dynamic
# ones in qemu-user; either will do, so take whichever is present.
find_program(QEMU_RISCV64 NAMES qemu-riscv64-static qemu-riscv64)
if(QEMU_RISCV64)
# The /lib64 symlink is created by the setup script; without it, pass -L
# explicitly so QEMU can find the riscv64 dynamic linker.
if(EXISTS "/lib64/ld-linux-riscv64-lp64d.so.1")
set(CMAKE_CROSSCOMPILING_EMULATOR "${QEMU_RISCV64}" CACHE STRING "" FORCE)
else()
set(CMAKE_CROSSCOMPILING_EMULATOR
"${QEMU_RISCV64};-L;/usr/riscv64-linux-gnu"
CACHE STRING "" FORCE)
endif()
endif()
@@ -1,15 +1,34 @@
#!/bin/bash #!/bin/bash
# Sets up the loongarch64 cross-compilation environment. # Sets up a cross-compilation environment for one of the headless-only targets.
# Run once with sudo. After this, ./b.sh --loongarch64 works on a fresh checkout. # Run once with sudo. After this, ./b.sh --<target> works on a fresh checkout.
#
# Usage: sudo cmake/scripts/setup-cross.sh <loongarch64|riscv64>
set -e set -e
SYSROOT=/usr/loongarch64-linux-gnu TARGET="$1"
CROSS_GCC=loongarch64-linux-gnu-gcc-14
case "$TARGET" in
loongarch64)
TRIPLE=loongarch64-linux-gnu
LD_SO=ld-linux-loongarch-lp64d.so.1
;;
riscv64)
TRIPLE=riscv64-linux-gnu
LD_SO=ld-linux-riscv64-lp64d.so.1
;;
*)
echo "Usage: sudo $0 <loongarch64|riscv64>"
exit 1
;;
esac
SYSROOT=/usr/$TRIPLE
CROSS_GCC=$TRIPLE-gcc-14
if ! command -v $CROSS_GCC &>/dev/null; then if ! command -v $CROSS_GCC &>/dev/null; then
echo "Error: $CROSS_GCC not found. Install it first:" echo "Error: $CROSS_GCC not found. Install it first:"
echo " sudo apt install gcc-14-loongarch64-linux-gnu g++-14-loongarch64-linux-gnu binutils-loongarch64-linux-gnu" echo " sudo apt install gcc-14-$TRIPLE g++-14-$TRIPLE binutils-$TRIPLE"
exit 1 exit 1
fi fi
@@ -18,6 +37,8 @@ if [ "$(id -u)" -ne 0 ]; then
exit 1 exit 1
fi fi
HOST_MULTIARCH=$(gcc -print-multiarch 2>/dev/null || dpkg-architecture -qDEB_HOST_MULTIARCH 2>/dev/null)
# ── OpenGL headers ─────────────────────────────────────────────────────────── # ── OpenGL headers ───────────────────────────────────────────────────────────
# GL headers are platform-independent C headers; copy from the host. # GL headers are platform-independent C headers; copy from the host.
echo "Installing GL headers into sysroot..." echo "Installing GL headers into sysroot..."
@@ -27,13 +48,11 @@ for h in gl.h glext.h glcorearb.h glu.h; do
done done
# ── GL stub library ────────────────────────────────────────────────────────── # ── GL stub library ──────────────────────────────────────────────────────────
# Generate a loongarch64 libGL.so that exports all symbols from the host # Generate a libGL.so for the target that exports all symbols from the host
# libGL so that GLEW's static archive can be linked via PLT entries (a direct # libGL so that GLEW's static archive can be linked via PLT entries (a direct
# B26 branch to address 0 would overflow on LoongArch). # branch to address 0 would overflow the relocation on these targets).
echo "Generating libGL.so stub..." echo "Generating libGL.so stub..."
HOST_MULTIARCH=$(gcc -print-multiarch 2>/dev/null || dpkg-architecture -qDEB_HOST_MULTIARCH 2>/dev/null)
HOST_GL="" HOST_GL=""
for candidate in \ for candidate in \
/usr/lib/$HOST_MULTIARCH/libGL.so.1 \ /usr/lib/$HOST_MULTIARCH/libGL.so.1 \
@@ -43,8 +62,8 @@ for candidate in \
done done
STUB_C=$(mktemp /tmp/gl_stub_XXXXXX.c) STUB_C=$(mktemp /tmp/gl_stub_XXXXXX.c)
echo "/* Loongarch64 GL/GLX stub - for cross-compilation only */" > "$STUB_C" echo "/* $TARGET GL/GLX stub - for cross-compilation only */" > "$STUB_C"
echo "void __loongarch64_gl_placeholder(void) {}" >> "$STUB_C" echo "void __cross_gl_placeholder(void) {}" >> "$STUB_C"
if [ -n "$HOST_GL" ]; then if [ -n "$HOST_GL" ]; then
nm -D "$HOST_GL" 2>/dev/null | awk '/^[0-9a-f]+ T /{ print "void "$3"(void){}" }' >> "$STUB_C" nm -D "$HOST_GL" 2>/dev/null | awk '/^[0-9a-f]+ T /{ print "void "$3"(void){}" }' >> "$STUB_C"
@@ -62,16 +81,16 @@ rm "$STUB_C"
echo " Installed to $SYSROOT/lib/libGL.so" echo " Installed to $SYSROOT/lib/libGL.so"
# ── Dynamic linker symlink ─────────────────────────────────────────────────── # ── Dynamic linker symlink ───────────────────────────────────────────────────
# Without this, binfmt_misc can't find the loongarch64 dynamic linker. # Without this, binfmt_misc can't find the target's dynamic linker.
LD_LINUX=$SYSROOT/lib/ld-linux-loongarch-lp64d.so.1 LD_LINUX=$SYSROOT/lib/$LD_SO
if [ -f "$LD_LINUX" ]; then if [ -f "$LD_LINUX" ]; then
mkdir -p /lib64 mkdir -p /lib64
ln -sf "$LD_LINUX" /lib64/ld-linux-loongarch-lp64d.so.1 ln -sf "$LD_LINUX" /lib64/$LD_SO
echo "Created /lib64/ld-linux-loongarch-lp64d.so.1 -> $LD_LINUX" echo "Created /lib64/$LD_SO -> $LD_LINUX"
fi fi
echo "" echo ""
echo "Setup complete." echo "Setup complete."
echo "Build: ./b.sh --loongarch64" echo "Build: ./b.sh --$TARGET"
echo "Run: qemu-loongarch64-static -L $SYSROOT <binary>" echo "Run: qemu-$TARGET -L $SYSROOT <binary>"
echo " (after setup the /lib64 symlink lets you run loongarch64 binaries directly)" echo " (after setup the /lib64 symlink lets you run $TARGET binaries directly)"