feat(windows-client): cross-compile + ship ARM64 (aarch64) off the x64 runner
apple / swift (push) Successful in 55s
ci / rust (push) Successful in 1m20s
ci / web (push) Successful in 29s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 2m7s
ci / docs-site (push) Successful in 30s
android / android (push) Successful in 3m20s
windows-msix / package (x64, C:\Users\Public\ffmpeg, x86_64-pc-windows-msvc, C:\t) (push) Successful in 1m52s
deb / build-publish (push) Successful in 3m40s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 3m16s
decky / build-publish (push) Successful in 12s
ci / bench (push) Successful in 4m58s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 5s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 4s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 5s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 4s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 5s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 3m21s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 8m34s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 8m38s
docker / deploy-docs (push) Successful in 18s

windows.yml + windows-msix.yml gain an x86_64/aarch64 target matrix. ARM64 is
cross-compiled on the one x64 Windows runner — the x64 MSVC toolset ships the
ARM64 cross compiler, aarch64-pc-windows-msvc is tier-2 with host tools, and
SDL3/libopus (build-from-source) cross-compile cleanly. The only arch-specific
external dep is FFmpeg's import libs: the matrix points FFMPEG_DIR at a per-arch
tree (x64 C:\Users\Public\ffmpeg, arm64 C:\Users\Public\ffmpeg-arm64, both
FFmpeg 7.x / avcodec-61). Per-arch short CARGO_TARGET_DIR avoids a shared target
dir; fmt + test run only for x64 (aarch64 can't execute on the x64 host).

pack-msix.ps1 gains -Arch x64|arm64 (stamps the manifest ProcessorArchitecture,
arch-suffixes the .msix/.cer); windows-msix.yml matrixes both arches and
publishes ..._x64.msix / ..._arm64.msix. setup-windows-runner.ps1 provisions the
rustup target + the ARM64 FFmpeg tree (idempotent).

Verified live on the runner (home-windows-1): debug+release cross-build green,
clippy -D warnings green, and MSIX pack produces a valid arm64 package (manifest
arch=arm64; bundled exe/SDL3/avcodec/reactor-bootstrap all PE machine 0xAA64).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-19 11:44:24 +00:00
parent aef552f04a
commit bd3f417d4b
8 changed files with 148 additions and 43 deletions
+35 -14
View File
@@ -1,18 +1,22 @@
# Build the punktfunk Windows client as a signed MSIX and publish it to Gitea's generic package
# registry, so Windows boxes can download + install a real package (Start tile, clean
# install/uninstall) instead of a loose exe. Runs on the self-hosted Windows runner (host mode;
# scripts/ci/setup-windows-runner.ps1) — the MSVC/WinUI/FFmpeg toolchain + the Windows SDK's
# makeappx/signtool are baked into the runner's daemon env, same as windows.yml.
# Build the punktfunk Windows client as signed MSIX packages (x64 + ARM64) and publish them to
# Gitea's generic package registry, so Windows boxes can download + install a real package (Start
# tile, clean install/uninstall) instead of a loose exe. Runs on the self-hosted Windows runner
# (host mode; scripts/ci/setup-windows-runner.ps1) — the MSVC/WinUI/FFmpeg toolchain + the Windows
# SDK's makeappx/signtool are baked into the runner's daemon env, same as windows.yml.
#
# Both arches come off the ONE x64 runner: x86_64 natively, aarch64 cross-compiled (the x64 MSVC
# toolset has the ARM64 cross compiler; the matrix points FFMPEG_DIR at the ARM64 FFmpeg tree). See
# windows.yml for the cross-build rationale + the BOM/MAX_PATH runner gotchas.
#
# Registry (public, unom org): https://git.unom.io/unom/-/packages (generic group)
# Packaging internals: clients/windows/packaging/README.md. BOM/MAX_PATH runner
# gotchas baked into the daemon env + windows.yml: see that workflow.
# Packaging internals: clients/windows/packaging/README.md.
#
# Versioning — MSIX requires a strictly 4-part numeric version (no ~/- suffixes), so:
# win-vX.Y.Z tag -> X.Y.Z.0 (a real Windows-client release; `win-v*` is its own tag namespace,
# kept off the host's `host-v*` and the Apple `v*` to avoid the
# version-shadow class of bug — see deb.yml).
# main push / dispatch -> 0.2.<run_number>.0 (rolling; climbs monotonically by run number).
# Both arches share the version; artifacts are arch-suffixed (..._x64.msix / ..._arm64.msix).
#
# Signing (packaging/pack-msix.ps1): if the MSIX_CERT_PFX_B64 / MSIX_CERT_PASSWORD Actions secrets
# are set (a real or shared code-signing .pfx whose subject DN == Publisher), the package is signed
@@ -41,17 +45,33 @@ env:
jobs:
package:
runs-on: windows-amd64
timeout-minutes: 60
timeout-minutes: 90
strategy:
fail-fast: false
matrix:
include:
- arch: x64
target: x86_64-pc-windows-msvc
ffmpeg: C:\Users\Public\ffmpeg
td: C:\t
- arch: arm64
target: aarch64-pc-windows-msvc
ffmpeg: C:\Users\Public\ffmpeg-arm64
td: C:\t-a64
steps:
- uses: actions/checkout@v4
- name: Configure + version
shell: pwsh
run: |
# windows-reactor's build.rs unwraps CARGO_WORKSPACE_DIR; CARGO_TARGET_DIR=C:\t dodges the
# MAX_PATH wall in the CMake-from-source crates (see windows.yml). Both via GITHUB_ENV.
# windows-reactor's build.rs unwraps CARGO_WORKSPACE_DIR; CARGO_TARGET_DIR (per-arch, short)
# dodges the MAX_PATH wall in the CMake-from-source crates (see windows.yml). FFMPEG_DIR
# selects the arch's import libs + is read by pack-msix.ps1 for the runtime DLLs. All via
# GITHUB_ENV.
"CARGO_WORKSPACE_DIR=$env:GITHUB_WORKSPACE" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
"CARGO_TARGET_DIR=C:\t" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
"CARGO_TARGET_DIR=${{ matrix.td }}" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
"FFMPEG_DIR=${{ matrix.ffmpeg }}" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
rustup target add ${{ matrix.target }}
$parts = if ($env:GITHUB_REF -like 'refs/tags/win-v*') {
($env:GITHUB_REF_NAME -replace '^win-v', '').Split('.')
} else {
@@ -60,11 +80,11 @@ jobs:
while ($parts.Count -lt 4) { $parts += '0' }
$v = ($parts[0..3] -join '.')
"MSIX_VERSION=$v" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
Write-Output "MSIX version $v"
Write-Output "MSIX version $v arch ${{ matrix.arch }} target ${{ matrix.target }}"
- name: Build (release)
shell: pwsh
run: cargo build --release -p punktfunk-client-windows
run: cargo build --release -p punktfunk-client-windows --target ${{ matrix.target }}
- name: Pack + sign MSIX
shell: pwsh
@@ -73,7 +93,8 @@ jobs:
MSIX_CERT_PASSWORD: ${{ secrets.MSIX_CERT_PASSWORD }}
run: |
& clients/windows/packaging/pack-msix.ps1 `
-Version $env:MSIX_VERSION -TargetDir C:\t\release -OutDir C:\t\msix
-Version $env:MSIX_VERSION -Arch ${{ matrix.arch }} `
-TargetDir ${{ matrix.td }}\${{ matrix.target }}\release -OutDir ${{ matrix.td }}\msix
- name: Publish to Gitea generic registry
shell: pwsh
+36 -13
View File
@@ -1,18 +1,30 @@
# Windows client CI — runs on the self-hosted Windows runner (home-windows-1, host mode; see
# scripts/ci/setup-windows-runner.ps1). Build + clippy + fmt + test the WinUI 3 client
# (windows-reactor + D3D11/SwapChainPanel + WASAPI + SDL3) on x86_64-pc-windows-msvc.
# (windows-reactor + D3D11/SwapChainPanel + WASAPI + SDL3).
#
# The MSVC/WinUI/FFmpeg toolchain (cargo/rustup on ASCII paths, NASM, CMake, LLVM, FFmpeg,
# CARGO_HOME, CMAKE_POLICY_VERSION_MINIMUM, …) is baked into the runner's daemon env. Two
# per-checkout vars are set in a step:
# Two architectures from ONE x64 runner: x86_64-pc-windows-msvc natively and
# aarch64-pc-windows-msvc by cross-compiling. The x64 MSVC toolset ships an ARM64 cross compiler
# (VC\Tools\MSVC\<ver>\bin\Hostx64\arm64\cl.exe) and aarch64-pc-windows-msvc is a tier-2 Rust
# target with host tools, so no ARM64 runner is needed — the cc/cmake crates pick the ARM64
# compiler from the target triple (SDL3 + libopus build-from-source cross-compile fine). The one
# arch-specific external dep is FFmpeg's import libs: the runner keeps an x64 tree at
# C:\Users\Public\ffmpeg and an ARM64 tree at C:\Users\Public\ffmpeg-arm64 (both FFmpeg 7.x /
# avcodec-61); the matrix points FFMPEG_DIR at the right one. aarch64 can't *run* on the x64 host,
# so fmt + test run only for x64.
#
# The MSVC/WinUI/FFmpeg toolchain (cargo/rustup on ASCII paths, NASM, CMake, LLVM, the x64 FFmpeg,
# CARGO_HOME, CMAKE_POLICY_VERSION_MINIMUM, …) is baked into the runner's daemon env. Per-checkout
# / per-arch vars are set in a step:
# - CARGO_WORKSPACE_DIR windows-reactor's build.rs unwraps it + stages the Win App SDK
# NuGets/winmd under it (from GITHUB_WORKSPACE).
# - CARGO_TARGET_DIR=C:\t the runner's host workdir is buried deep under
# - CARGO_TARGET_DIR=C:\t the runner's host workdir is buried deep under
# C:\Windows\System32\config\systemprofile\.cache\act\<hash>\hostexecutor\,
# so the default target\ path blows past Windows' MAX_PATH (260) inside the
# CMake-from-source builds (audiopus_sys / SDL3) — MSBuild's tracker then
# can't create its .tlog (DirectoryNotFoundException -> MSB6003). A short
# root keeps every nested path well under the limit.
# root keeps every nested path well under the limit (per-arch so the two
# matrix legs don't share a target dir).
# - FFMPEG_DIR per-arch FFmpeg import libs (x64 vs arm64 tree).
#
# Steps use `shell: pwsh` (PowerShell 7) deliberately: Windows PowerShell 5.1's
# `Out-File -Encoding utf8` prepends a UTF-8 BOM that corrupts the first GITHUB_ENV line (the
@@ -41,7 +53,11 @@ on:
jobs:
build:
runs-on: windows-amd64
timeout-minutes: 60
timeout-minutes: 90
strategy:
fail-fast: false
matrix:
target: [x86_64-pc-windows-msvc, aarch64-pc-windows-msvc]
steps:
- uses: actions/checkout@v4
@@ -49,24 +65,31 @@ jobs:
shell: pwsh
run: |
"CARGO_WORKSPACE_DIR=$env:GITHUB_WORKSPACE" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
"CARGO_TARGET_DIR=C:\t" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
# Per-arch short target root (dodges MAX_PATH; keeps the two legs from sharing target\).
$td = if ('${{ matrix.target }}' -eq 'aarch64-pc-windows-msvc') { 'C:\t-a64' } else { 'C:\t' }
"CARGO_TARGET_DIR=$td" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
# Per-arch FFmpeg import libs (the runner provisions both — setup-windows-runner.ps1).
$ff = if ('${{ matrix.target }}' -eq 'aarch64-pc-windows-msvc') { 'C:\Users\Public\ffmpeg-arm64' } else { 'C:\Users\Public\ffmpeg' }
"FFMPEG_DIR=$ff" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
rustup target add ${{ matrix.target }}
rustc --version
cargo --version
node --version
Write-Output "workspace: $env:GITHUB_WORKSPACE"
Write-Output "target ${{ matrix.target }} target-dir $td ffmpeg $ff"
- name: Build
shell: pwsh
run: cargo build -p punktfunk-client-windows
run: cargo build -p punktfunk-client-windows --target ${{ matrix.target }}
- name: Clippy (-D warnings)
shell: pwsh
run: cargo clippy -p punktfunk-client-windows --all-targets -- -D warnings
run: cargo clippy -p punktfunk-client-windows --all-targets --target ${{ matrix.target }} -- -D warnings
- name: Rustfmt check
if: matrix.target == 'x86_64-pc-windows-msvc'
shell: pwsh
run: cargo fmt -p punktfunk-client-windows -- --check
- name: Test
if: matrix.target == 'x86_64-pc-windows-msvc'
shell: pwsh
run: cargo test -p punktfunk-client-windows
run: cargo test -p punktfunk-client-windows --target ${{ matrix.target }}