[CI] Add native Windows ARM64 MSYS2, MSVC and Libretro builds

This commit is contained in:
Joseph Gershgorin 2026-04-25 05:17:10 -07:00
parent 91128d6625
commit 75fbbd9cbf
7 changed files with 142 additions and 18 deletions

View file

@ -6,6 +6,16 @@ if [ "$GITHUB_REF_TYPE" == "tag" ]; then
export EXTRA_CMAKE_FLAGS=(-DENABLE_QT_UPDATE_CHECKER=ON)
fi
# Map the workflow ARCH (x64/arm64) onto CMake's ARCHITECTURE variable so
# Qt downloads and arch-dependent paths pick the right target even if the
# compiler-based detection gets confused (e.g. on windows-11-arm runners
# where the MSVC setup may land on an x64 host compiler).
if [ "$ARCH" = "arm64" ]; then
export EXTRA_CMAKE_FLAGS=("${EXTRA_CMAKE_FLAGS[@]}" -DARCHITECTURE=arm64)
elif [ "$ARCH" = "x64" ]; then
export EXTRA_CMAKE_FLAGS=("${EXTRA_CMAKE_FLAGS[@]}" -DARCHITECTURE=x86_64)
fi
cmake .. -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_C_COMPILER_LAUNCHER=ccache \
@ -15,7 +25,13 @@ cmake .. -G Ninja \
"${EXTRA_CMAKE_FLAGS[@]}"
ninja
ninja bundle
strip -s bundle/*.exe
# MSVC keeps debug info in separate PDB files, so the .exe has nothing
# to strip. The `strip` that ends up in PATH on the runner is also from
# Git-for-Windows' x86_64 mingw and can't read arm64 PE binaries.
case "$TARGET" in
msvc*) ;;
*) strip -s bundle/*.exe ;;
esac
ccache -s -v

View file

@ -143,11 +143,35 @@ jobs:
path: artifacts/
windows:
runs-on: windows-latest
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
target: ["msvc", "msys2"]
include:
- target: msvc
arch: x64
runner: windows-latest
msvc_arch: x64
msys2_system: clang64
vulkan_sdk_url: https://sdk.lunarg.com/sdk/download/1.4.341.1/windows/vulkansdk-windows-X64-1.4.341.1.exe
- target: msys2
arch: x64
runner: windows-latest
msvc_arch: x64
msys2_system: clang64
vulkan_sdk_url: https://sdk.lunarg.com/sdk/download/1.4.341.1/windows/vulkansdk-windows-X64-1.4.341.1.exe
- target: msvc
arch: arm64
runner: windows-11-arm
msvc_arch: amd64_arm64
msys2_system: clangarm64
vulkan_sdk_url: https://sdk.lunarg.com/sdk/download/1.4.341.1/warm/vulkansdk-windows-ARM64-1.4.341.1.exe
- target: msys2
arch: arm64
runner: windows-11-arm
msvc_arch: amd64_arm64
msys2_system: clangarm64
vulkan_sdk_url: https://sdk.lunarg.com/sdk/download/1.4.341.1/warm/vulkansdk-windows-ARM64-1.4.341.1.exe
defaults:
run:
shell: ${{ (matrix.target == 'msys2' && 'msys2') || 'bash' }} {0}
@ -156,7 +180,8 @@ jobs:
CCACHE_COMPILERCHECK: content
CCACHE_SLOPPINESS: time_macros
OS: windows
TARGET: ${{ matrix.target }}
TARGET: ${{ matrix.target }}-${{ matrix.arch }}
ARCH: ${{ matrix.arch }}
steps:
- uses: actions/checkout@v4
with:
@ -165,25 +190,27 @@ jobs:
uses: actions/cache@v4
with:
path: ${{ env.CCACHE_DIR }}
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}
key: ${{ runner.os }}-${{ matrix.target }}-${{ matrix.arch }}-${{ github.sha }}
restore-keys: |
${{ runner.os }}-${{ matrix.target }}-
${{ runner.os }}-${{ matrix.target }}-${{ matrix.arch }}-
- name: Set up MSVC
if: ${{ matrix.target == 'msvc' }}
uses: ilammy/msvc-dev-cmd@v1
with:
arch: ${{ matrix.msvc_arch }}
- name: Install extra tools (MSVC)
if: ${{ matrix.target == 'msvc' }}
run: choco install ccache ninja ptime wget
- name: Install vulkan-sdk (MSVC)
if: ${{ matrix.target == 'msvc' }}
run: |
wget https://sdk.lunarg.com/sdk/download/1.4.304.1/windows/VulkanSDK-1.4.304.1-Installer.exe -O D:/a/_temp/vulkan.exe
D:/a/_temp/vulkan.exe --accept-licenses --default-answer --confirm-command install
wget ${{ matrix.vulkan_sdk_url }} -O "${{ runner.temp }}/vulkan.exe"
"${{ runner.temp }}/vulkan.exe" --accept-licenses --default-answer --confirm-command install
- name: Set up MSYS2
if: ${{ matrix.target == 'msys2' }}
uses: msys2/setup-msys2@v2
with:
msystem: clang64
msystem: ${{ matrix.msys2_system }}
update: true
install: git make p7zip
pacboy: >-
@ -197,8 +224,8 @@ jobs:
- name: Install NSIS
if: ${{ github.ref_type == 'tag' }}
run: |
wget https://download.sourceforge.net/project/nsis/NSIS%203/3.11/nsis-3.11-setup.exe -O D:/a/_temp/nsis-setup.exe
ptime D:/a/_temp/nsis-setup.exe /S
wget https://download.sourceforge.net/project/nsis/NSIS%203/3.11/nsis-3.11-setup.exe -O "${{ runner.temp }}/nsis-setup.exe"
ptime "${{ runner.temp }}/nsis-setup.exe" /S
shell: pwsh
- name: Disable line ending translation
run: git config --global core.autocrlf input
@ -208,7 +235,7 @@ jobs:
if: ${{ github.ref_type == 'tag' }}
run: |
cd src\installer
"C:\Program Files (x86)\NSIS\makensis.exe" /DPRODUCT_VARIANT=${{ matrix.target }} /DPRODUCT_VERSION=${{ github.ref_name }} citra.nsi
"C:\Program Files (x86)\NSIS\makensis.exe" /DPRODUCT_VARIANT=${{ matrix.target }}-${{ matrix.arch }} /DPRODUCT_VERSION=${{ github.ref_name }} citra.nsi
mkdir ..\..\artifacts 2> NUL
move /y *.exe ..\..\artifacts\
shell: cmd

View file

@ -102,6 +102,48 @@ jobs:
with:
name: ${{ env.OS }}-${{ env.TARGET }}
path: ./*.zip
windows-arm64:
runs-on: windows-11-arm
defaults:
run:
shell: msys2 {0}
env:
OS: windows
TARGET: arm64
BUILD_DIR: build/windows-arm64
# LIBRETRO_STATIC + -static fold the clangarm64 runtime (libc++,
# libunwind, libwinpthread) into the core, matching what MXE does
# for the x86_64 libretro build. Without these, the produced DLL
# imports MSYS2 runtime DLLs that aren't ABI-compatible with the
# ones a target machine has alongside RetroArch.
EXTRA_CORE_ARGS: -DENABLE_LTO=OFF -G Ninja -DLIBRETRO_STATIC=1 -DCMAKE_SHARED_LINKER_FLAGS=-static
EXTRA_PATH: bin/Release
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Disable line ending translation
shell: pwsh
run: git config --global core.autocrlf input
- name: Set up MSYS2
uses: msys2/setup-msys2@v2
with:
msystem: clangarm64
update: true
install: git make zip
pacboy: >-
toolchain:p cmake:p ninja:p spirv-tools:p
- name: Build
run: |
cmake $CORE_ARGS $EXTRA_CORE_ARGS . -B $BUILD_DIR
cmake --build $BUILD_DIR --target azahar_libretro --config Release -j $(nproc)
- name: Pack
run: ./.ci/libretro-pack.sh
- name: Upload
uses: actions/upload-artifact@v4
with:
name: ${{ env.OS }}-${{ env.TARGET }}
path: ./*.zip
macos:
runs-on: macos-26
strategy:

View file

@ -2,6 +2,14 @@
if (BUNDLE_TARGET_EXECUTE)
# --- Bundling method logic ---
# Opt into CMP0207's NEW behavior so file(GET_RUNTIME_DEPENDENCIES)
# normalizes paths before regex matching. The exclude pattern below
# ('.*system32.*') already matches either slash style, so this is
# purely about silencing the dev-mode warning.
if (POLICY CMP0207)
cmake_policy(SET CMP0207 NEW)
endif()
function(symlink_safe_copy from to)
if (WIN32)
# Use cmake copy for maximum compatibility.

View file

@ -2,7 +2,7 @@
set(CURRENT_MODULE_DIR ${CMAKE_CURRENT_LIST_DIR})
# Determines parameters based on the host and target for downloading the right Qt binaries.
function(determine_qt_parameters target host_out type_out arch_out arch_path_out host_type_out host_arch_out host_arch_path_out)
function(determine_qt_parameters target host_out type_out arch_out arch_path_out tools_host_out host_type_out host_arch_out host_arch_path_out)
if (target MATCHES "tools_.*")
set(tool ON)
else()
@ -21,6 +21,10 @@ function(determine_qt_parameters target host_out type_out arch_out arch_path_out
elseif (MSVC)
if ("arm64" IN_LIST ARCHITECTURE)
set(arch_path "msvc2022_arm64")
# aqt serves Windows ARM64 Qt packages under a separate
# host namespace; using "windows" here makes aqt parse the
# x86_64 package XML and fail to find the arm64 archives.
set(host "windows_arm64")
elseif ("x86_64" IN_LIST ARCHITECTURE)
set(arch_path "msvc2022_64")
else()
@ -39,6 +43,9 @@ function(determine_qt_parameters target host_out type_out arch_out arch_path_out
set(host_arch_path "msvc2022_64")
endif()
set(host_arch "win64_${host_arch_path}")
# The x86_64 desktop Qt used for host tools lives under the
# regular "windows" aqt host, even when the target is arm64.
set(tools_host "windows")
else()
message(FATAL_ERROR "Unsupported bundled Qt toolchain. Enable USE_SYSTEM_QT and provide your own.")
endif()
@ -69,6 +76,11 @@ function(determine_qt_parameters target host_out type_out arch_out arch_path_out
set(${type_out} "${type}" PARENT_SCOPE)
set(${arch_out} "${arch}" PARENT_SCOPE)
set(${arch_path_out} "${arch_path}" PARENT_SCOPE)
if (DEFINED tools_host)
set(${tools_host_out} "${tools_host}" PARENT_SCOPE)
else()
set(${tools_host_out} "${host}" PARENT_SCOPE)
endif()
if (DEFINED host_type)
set(${host_type_out} "${host_type}" PARENT_SCOPE)
else()
@ -100,8 +112,17 @@ function(download_qt_configuration prefix_out target host type arch arch_path ba
set(install_args ${install_args} install-tool --outputdir ${base_path} ${host} desktop ${target})
else()
set(prefix "${base_path}/${target}/${arch_path}")
set(install_args ${install_args} install-qt --outputdir ${base_path} ${host} ${type} ${target} ${arch}
-m qtmultimedia --archives qttranslations qttools qtsvg qtbase)
# aqt's arm64 Qt package layout differs from x86_64 the per-module archive
# names (qtbase/qtsvg/…) don't exist in its XML, so passing --archives trips
# the parser. Install the default archive set for arm64 and let --autodesktop
# pull in the matching x86_64 desktop Qt that the arm64 target depends on.
if ("${arch_path}" STREQUAL "msvc2022_arm64")
set(install_args ${install_args} install-qt --outputdir ${base_path} ${host} ${type} ${target} ${arch}
--autodesktop -m qtmultimedia)
else()
set(install_args ${install_args} install-qt --outputdir ${base_path} ${host} ${type} ${target} ${arch}
-m qtmultimedia --archives qttranslations qttools qtsvg qtbase)
endif()
endif()
if (NOT EXISTS "${prefix}")
@ -150,14 +171,14 @@ endfunction()
# Params:
# target: Qt dependency to install. Specify a version number to download Qt, or "tools_(name)" for a specific build tool.
function(download_qt target)
determine_qt_parameters("${target}" host type arch arch_path host_type host_arch host_arch_path)
determine_qt_parameters("${target}" host type arch arch_path tools_host host_type host_arch host_arch_path)
get_external_prefix(qt base_path)
file(MAKE_DIRECTORY "${base_path}")
download_qt_configuration(prefix "${target}" "${host}" "${type}" "${arch}" "${arch_path}" "${base_path}")
if (DEFINED host_arch_path AND NOT "${host_arch_path}" STREQUAL "${arch_path}")
download_qt_configuration(host_prefix "${target}" "${host}" "${host_type}" "${host_arch}" "${host_arch_path}" "${base_path}")
download_qt_configuration(host_prefix "${target}" "${tools_host}" "${host_type}" "${host_arch}" "${host_arch_path}" "${base_path}")
else()
set(host_prefix "${prefix}")
endif()

View file

@ -96,6 +96,9 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Darwin" OR
CMAKE_SYSTEM_NAME STREQUAL "iOS" OR
CMAKE_SYSTEM_NAME STREQUAL "tvOS")
target_link_libraries(azahar_libretro PRIVATE "-Wl,-exported_symbols_list,${CMAKE_CURRENT_SOURCE_DIR}/libretro.osx.def")
else()
elseif (NOT WIN32)
# -Bsymbolic is an ELF-only flag controlling how a shared object resolves
# its own symbols. PE/COFF has no equivalent; GNU ld silently ignored it
# on Windows MinGW, but lld (clangarm64's default linker) rejects it.
target_link_libraries(azahar_libretro PRIVATE "-Wl,-Bsymbolic")
endif()

View file

@ -38,6 +38,12 @@
#include <tchar.h>
#include "common/string_util.h"
// Pull <sys/stat.h> in here, before any fstat-redirection macros come into
// scope. MinGW's <sys/stat.h> declares fstat as a real function (not a
// macro); if our `#define fstat _fstat64` were active during its parsing,
// the declaration would be rewritten to a conflicting `_fstat64` overload.
#include <sys/stat.h>
#ifdef _MSC_VER
// 64 bit offsets for MSVC
#define fseeko _fseeki64
@ -47,6 +53,7 @@ typedef struct _stat64 file_stat_t;
#define fstat _fstat64
#elif defined(HAVE_LIBRETRO)
typedef struct _stat64 file_stat_t;
#define fstat _fstat64
#else
typedef struct stat file_stat_t;
#endif