name: Windows ARM64 Installer (MSYS2 + Inno Setup) on: push: branches: [master] pull_request: branches: [master] jobs: windows_arm64_installer: runs-on: windows-11-arm steps: - uses: actions/checkout@v4 - uses: msys2/setup-msys2@v2 with: msystem: CLANGARM64 update: true install: >- git mingw-w64-clang-aarch64-toolchain mingw-w64-clang-aarch64-meson mingw-w64-clang-aarch64-ninja mingw-w64-clang-aarch64-pkgconf mingw-w64-clang-aarch64-gettext-tools mingw-w64-clang-aarch64-libxml2 mingw-w64-clang-aarch64-winpthreads mingw-w64-clang-aarch64-python mingw-w64-clang-aarch64-python-cffi mingw-w64-clang-aarch64-gtk2 mingw-w64-clang-aarch64-gtk-update-icon-cache mingw-w64-clang-aarch64-luajit mingw-w64-clang-aarch64-desktop-file-utils mingw-w64-clang-aarch64-ntldd - name: Configure shell: msys2 {0} run: | set -euxo pipefail export PATH="/clangarm64/bin:$PATH" export MSGFMT="/clangarm64/bin/msgfmt" export GETTEXTDATADIR="/clangarm64/share/gettext" # Defensive: ensure python + cffi exist even if the setup action had a bad day pacman -S --noconfirm --needed mingw-w64-clang-aarch64-python mingw-w64-clang-aarch64-python-cffi rm -rf build dist mkdir -p dist meson setup build \ --prefix=/ \ --bindir=bin \ --libdir=lib \ --datadir=share \ -Dtext-frontend=true \ -Ddbus=disabled \ -Dwith-upd=false \ -Dwith-perl=false \ -Dc_link_args="-pthread -lwinpthread" \ -Dcpp_link_args="-pthread -lwinpthread" /clangarm64/bin/python.exe -c "import sys, cffi; print('python:', sys.version); print('cffi:', cffi.__version__)" # Export app version from Meson for the Inno script defines APPVER="$(meson introspect build --projectinfo | /clangarm64/bin/python.exe -c 'import json,sys; print(json.load(sys.stdin)[\"version\"])')" echo "APPVER=$APPVER" >> "$GITHUB_ENV" echo "Meson APPVER=$APPVER" - name: Build shell: msys2 {0} run: | set -euxo pipefail export PATH="/clangarm64/bin:$PATH" ninja -C build - name: Test shell: msys2 {0} run: | set -euxo pipefail export PATH="/clangarm64/bin:$PATH" ninja -C build test || true - name: Stage install shell: msys2 {0} run: | set -euxo pipefail export PATH="/clangarm64/bin:$PATH" DESTDIR="$PWD/dist" ninja -C build install echo "Installed files (debug):" find dist -maxdepth 8 -type f \( -iname 'zoitechat*.exe' -o -iname '*.dll' \) -print || true - name: Harvest runtime DLL dependencies shell: msys2 {0} run: | set -euxo pipefail export PATH="/clangarm64/bin:$PATH" shopt -s nullglob mapfile -t targets < <(find dist -type f \( -iname 'zoitechat*.exe' -o -ipath '*/plugins/*.dll' \) 2>/dev/null || true) if [ "${#targets[@]}" -eq 0 ]; then echo "No installed binaries/plugins found under dist/ (skipping dep harvest)" exit 0 fi mainexe="$(printf '%s\n' "${targets[@]}" | grep -iE '/zoitechat\.exe$' | head -n1 || true)" if [ -z "$mainexe" ]; then echo "zoitechat.exe not found under dist/; skipping dep harvest" exit 0 fi bindir="$(dirname "$mainexe")" echo "Using bindir: $bindir" for f in "${targets[@]}"; do deps="$(ntldd -R "$f" 2>/dev/null || true)" [ -n "$deps" ] || continue while IFS= read -r dll; do [ -n "$dll" ] || continue cp -n "$dll" "$bindir/" || true done < <( printf '%s\n' "$deps" \ | tr '\\' '/' \ | awk '{print $1}' \ | grep -E '^/clangarm64/(bin|lib)/.*\.dll$' \ || true ) done - name: Prepare Inno Setup source tree (win32/rel) shell: msys2 {0} run: | set -euxo pipefail export PATH="/clangarm64/bin:$PATH" rm -rf win32/rel mkdir -p win32/rel app_exe="$(find dist -type f -iname 'zoitechat.exe' | head -n1 || true)" if [ -z "$app_exe" ]; then echo "zoitechat.exe not found under dist/; cannot build installer." find dist -maxdepth 8 -type f -print || true exit 1 fi bin_dir="$(dirname "$app_exe")" install_root="$(cd "$bin_dir/.." && pwd)" echo "app_exe: $app_exe" echo "install_root: $install_root" cp -a "$install_root/." win32/rel/ echo "win32/rel contents (debug):" find win32/rel -maxdepth 5 -type f \( -iname 'zoitechat*.exe' -o -iname '*.dll' \) -print || true - name: Install Inno Setup (find real ISCC.exe) shell: pwsh run: | $ErrorActionPreference = "Stop" if (-not (Get-Command choco.exe -ErrorAction SilentlyContinue)) { Write-Host "Chocolatey not found; installing..." Set-ExecutionPolicy Bypass -Scope Process -Force [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072 iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1')) } choco install innosetup -y --force --no-progress | Out-Host $candidates = @() $pf = ${env:ProgramFiles} $pfx86 = ${env:ProgramFiles(x86)} if ($pf) { $candidates += (Join-Path $pf "Inno Setup 6\ISCC.exe") } if ($pfx86) { $candidates += (Join-Path $pfx86 "Inno Setup 6\ISCC.exe") } $chocoRoot = ${env:ChocolateyInstall} if ($chocoRoot -and (Test-Path $chocoRoot)) { $candidates += (Join-Path $chocoRoot "lib\innosetup\tools\ISCC.exe") $candidates += (Get-ChildItem -Path (Join-Path $chocoRoot "lib\innosetup") -Recurse -Filter "ISCC.exe" -ErrorAction SilentlyContinue | Where-Object { $_.FullName -notmatch '\\chocolatey\\bin\\' } | Select-Object -ExpandProperty FullName -First 10) } $regKeys = @( "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Inno Setup 6_is1", "HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Inno Setup 6_is1" ) foreach ($k in $regKeys) { try { $loc = (Get-ItemProperty -Path $k -ErrorAction Stop).InstallLocation if ($loc) { $candidates += (Join-Path $loc "ISCC.exe") } } catch {} } $iscc = $candidates | Where-Object { $_ -and (Test-Path $_) } | Select-Object -First 1 if (-not $iscc) { Write-Host "Checked candidates:" $candidates | ForEach-Object { Write-Host " - $_" } throw "ISCC.exe not found after install attempts." } Write-Host "Using ISCC:" $iscc (Get-Item $iscc).VersionInfo | Select-Object ProductVersion, FileVersion | Format-List | Out-Host "ISCC_PATH=$iscc" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 - name: Patch ARM64 .iss for CI (disable missing idp.iss) shell: pwsh run: | $ErrorActionPreference = "Stop" $issIn = "win32\installer\zoitechat-arm64.iss" if (-not (Test-Path $issIn)) { throw "Missing: $issIn" } $projectDir = (Resolve-Path "win32\installer").Path # double trailing backslash avoids Windows arg-quoting weirdness, and still works as a path $projectDirArg = ($projectDir.TrimEnd('\') + "\\") "PROJECTDIR=$projectDirArg" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 $text = Get-Content $issIn -Raw # Comment out the IDP include (it isn't present on the runner; and IDP plugin support on ARM64 is not reliable) $text = [regex]::Replace($text, '^\s*#include\s+\s*$', '; CI: idp.iss disabled for ARM64 builds', 'Multiline') if ($text -notmatch '; CI: IDP STUBS') { $stub = @" ; CI: IDP STUBS (no-op on ARM64 CI) procedure idpDownloadAfter(PageID: Integer); begin end; procedure idpClearFiles; begin end; procedure idpAddFile(const Url, DestFile: string); begin end; procedure idpAddFileComp(const Url, DestFile: string; const Components: string); begin end; procedure idpAddFileSize(const Url, DestFile: string; const Size: Int64); begin end; procedure idpAddFileSizeComp(const Url, DestFile: string; const Size: Int64; const Components: string); begin end; function idpDownloadFiles: Boolean; begin Result := True; end; "@ # Inject stubs immediately after the [Code] header (so [Setup] stays first) $text = [regex]::Replace($text, '(\[Code\]\s*\r?\n)', "`$1$stub", 'Singleline') } $issOut = "win32\installer\zoitechat-arm64.ci.iss" Set-Content -Path $issOut -Value $text -Encoding UTF8 Write-Host "Wrote patched CI script: $issOut" - name: Build ARM64 installer shell: pwsh run: | $ErrorActionPreference = "Stop" $iscc = $env:ISCC_PATH if (-not $iscc -or -not (Test-Path $iscc)) { throw "ISCC_PATH missing or invalid." } $iss = "win32\installer\zoitechat-arm64.ci.iss" if (-not (Test-Path $iss)) { throw "Patched CI script missing: $iss" } if (-not $env:APPVER) { throw "APPVER missing (Meson version export failed)." } if (-not $env:PROJECTDIR) { throw "PROJECTDIR missing." } & $iscc "/DAPPARCH=arm64" "/DAPPVER=$env:APPVER" "/DPROJECTDIR=$env:PROJECTDIR" $iss | Out-Host if ($LASTEXITCODE -ne 0) { throw "ISCC failed with exit code $LASTEXITCODE" } $exe = Get-ChildItem -Path "win32" -Recurse -Filter "*.exe" | Where-Object { $_.Name -match 'Setup' -or $_.Name -match 'Installer' } | Sort-Object LastWriteTime -Descending | Select-Object -First 1 if (-not $exe) { Write-Host "win32 tree (debug):" Get-ChildItem -Path "win32" -Recurse -Depth 4 | Select-Object FullName | Out-Host throw "Installer exe not found under win32/" } Write-Host "Installer built:" $exe.FullName "INSTALLER_EXE=$($exe.FullName)" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 - uses: actions/upload-artifact@v4 with: name: zoitechat-windows-arm64-installer path: ${{ env.INSTALLER_EXE }}