Files
zoitechat/.github/workflows/windows-build.yml
2026-02-01 21:29:24 -07:00

365 lines
15 KiB
YAML

name: Windows Build
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
windows_build:
runs-on: windows-2019
permissions:
contents: read
id-token: write
attestations: write
artifact-metadata: write
strategy:
matrix:
platform: [x64, win32]
arch: [x64, x86]
exclude:
- platform: x64
arch: x86
- platform: win32
arch: x64
fail-fast: false
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.14.2"
architecture: ${{ matrix.arch }}
- name: Install Dependencies (GTK3 toolchain + packagers)
env:
GITHUB_TOKEN: ${{ github.token }}
shell: pwsh
run: |
$ErrorActionPreference = "Stop"
New-Item -Name "deps" -ItemType "Directory" -Force | Out-Null
# Inno Setup + IDP (kept as-is)
Invoke-WebRequest http://files.jrsoftware.org/is/5/innosetup-5.5.9-unicode.exe -OutFile deps\innosetup-unicode.exe
& deps\innosetup-unicode.exe /VERYSILENT | Out-Null
Invoke-WebRequest https://github.com/zoitechat/gvsbuild/releases/download/zoitechat-2.17.0/idpsetup-1.5.1.exe -OutFile deps\idpsetup.exe
& deps\idpsetup.exe /VERYSILENT
# WinSparkle / gendef / perl (kept as-is)
Invoke-WebRequest https://github.com/zoitechat/gvsbuild/releases/download/zoitechat-2.17.0/gendef-20111031.7z -OutFile deps\gendef.7z
& 7z.exe x deps\gendef.7z -oC:\gtk-build | Out-Null
Invoke-WebRequest https://github.com/zoitechat/gvsbuild/releases/download/zoitechat-2.17.0/WinSparkle-20151011.7z -OutFile deps\WinSparkle.7z
& 7z.exe x deps\WinSparkle.7z -oC:\gtk-build\WinSparkle | Out-Null
Invoke-WebRequest https://github.com/zoitechat/gvsbuild/releases/download/zoitechat-2.17.0/perl-5.20.0-${{ matrix.arch }}.7z -OutFile deps\perl-${{ matrix.arch }}.7z
& 7z.exe x deps\perl-${{ matrix.arch }}.7z -oC:\gtk-build\perl-5.20\${{ matrix.platform }} | Out-Null
# ------------------------------------------
# GTK: download wingtk/gvsbuild GTK3 bundle and normalize into:
# C:\gtk-build\gtk\<platform>\release\{bin,include,lib}
# ------------------------------------------
$wantArch = if ("${{ matrix.platform }}" -eq "x64") { "x64" } else { "x86" }
$wantPlat = "${{ matrix.platform }}"
$extractRoot = "C:\_gtk_extract"
if (Test-Path $extractRoot) { Remove-Item $extractRoot -Recurse -Force }
New-Item -Path $extractRoot -ItemType Directory -Force | Out-Null
function Get-PEMachine([string]$path) {
try {
$fs = [System.IO.File]::Open($path, 'Open', 'Read', 'ReadWrite')
try {
$br = New-Object System.IO.BinaryReader($fs)
$mz = $br.ReadUInt16()
if ($mz -ne 0x5A4D) { return $null } # MZ
$fs.Seek(0x3C, [System.IO.SeekOrigin]::Begin) | Out-Null
$peOff = $br.ReadUInt32()
$fs.Seek($peOff, [System.IO.SeekOrigin]::Begin) | Out-Null
$peSig = $br.ReadUInt32()
if ($peSig -ne 0x00004550) { return $null } # PE\0\0
$machine = $br.ReadUInt16()
return $machine
} finally {
$fs.Close()
}
} catch {
return $null
}
}
function Ensure-Dir([string]$p) {
if (-not (Test-Path $p)) { New-Item -Path $p -ItemType Directory -Force | Out-Null }
}
function Pick-GtkPrefix([string]$root) {
$libs = Get-ChildItem -Path $root -Recurse -File -Filter "*.lib" -ErrorAction SilentlyContinue
$preferred = @('^gtk-3\.0\.lib$', '^gtk-3-0\.lib$', '^gtk-3\.lib$')
foreach ($rx in $preferred) {
$cand = $libs | Where-Object { $_.Name -match $rx } | Select-Object -First 1
if ($cand) { return (Split-Path -Parent $cand.Directory.FullName) }
}
$cand2 = $libs | Where-Object { $_.Name -match '^gtk-3.*\.lib$' } | Select-Object -First 1
if ($cand2) { return (Split-Path -Parent $cand2.Directory.FullName) }
return $null
}
function Copy-AliasLib([string]$gtkLibDir, [string]$targetName, [string[]]$sourceNameRegexes) {
$target = Join-Path $gtkLibDir $targetName
if (Test-Path $target) { return }
$allLibs = Get-ChildItem -Path $gtkLibDir -File -Filter "*.lib" -ErrorAction SilentlyContinue
$src = $null
foreach ($rx in $sourceNameRegexes) {
$src = $allLibs | Where-Object { $_.Name -match $rx } | Select-Object -First 1
if ($src) { break }
}
if ($src) {
Copy-Item $src.FullName $target -Force
Write-Host "Alias: $targetName <= $($src.Name)"
} else {
Write-Host "Alias not created: $targetName (no match in $gtkLibDir)"
}
}
function Ensure-GenmarshalReady([string]$gtkBinDir, [string]$gtkPrefix, [string]$wantPlat) {
Ensure-Dir $gtkBinDir
$wantMachine = if ($wantPlat -eq "x64") { 0x8664 } else { 0x014c }
$binScript = Join-Path $gtkBinDir "glib-genmarshal"
$binExe = Join-Path $gtkBinDir "glib-genmarshal.exe"
# 1) If we already have glib-genmarshal (no extension) in bin, prefer it.
if (Test-Path $binScript) {
$m = Get-PEMachine $binScript
if ($m) {
# It's a PE binary without .exe. Make an .exe copy for subprocess callers, and keep python wrapper.
if ($m -ne $wantMachine) {
throw "glib-genmarshal in bin is PE but wrong arch (machine=0x{0:X})." -f $m
}
Copy-Item $binScript $binExe -Force
$wrapperLines = @(
"import os, subprocess, sys",
"tool = os.path.join(os.path.dirname(__file__), 'glib-genmarshal.exe')",
"sys.exit(subprocess.call([tool] + sys.argv[1:]))"
)
$wrapperLines | Set-Content -Path $binScript -Encoding ASCII
return
}
# Not PE: likely a script. If it's python-ish, we're done because vcxproj calls python.exe <script>.
$head = Get-Content -Path $binScript -TotalCount 5 -ErrorAction SilentlyContinue
$headText = ($head -join "`n")
$looksPython =
($headText -match 'python') -or
($headText -match '^\s*#!') -or
($headText -match '^\s*import\s+') -or
($headText -match '^\s*from\s+\w+\s+import\s+')
if ($looksPython) {
Write-Host "glib-genmarshal in bin looks like a script; using it as-is."
return
}
throw "Found glib-genmarshal in bin, but it does not look like a PE or a Python script. Cannot run it via python.exe."
}
# 2) Otherwise, search under prefix for .exe OR script and copy into bin.
$exeCands = Get-ChildItem -Path $gtkPrefix -Recurse -File -Filter "glib-genmarshal*.exe" -ErrorAction SilentlyContinue
if ($exeCands -and $exeCands.Count -gt 0) {
$picked = $null
foreach ($c in $exeCands) {
$m = Get-PEMachine $c.FullName
if ($m -eq $wantMachine) { $picked = $c; break }
}
if (-not $picked) { $picked = $exeCands | Select-Object -First 1 }
Copy-Item $picked.FullName $binExe -Force
$wrapperLines = @(
"import os, subprocess, sys",
"tool = os.path.join(os.path.dirname(__file__), 'glib-genmarshal.exe')",
"sys.exit(subprocess.call([tool] + sys.argv[1:]))"
)
$wrapperLines | Set-Content -Path $binScript -Encoding ASCII
return
}
$scriptCands = Get-ChildItem -Path $gtkPrefix -Recurse -File -Filter "glib-genmarshal" -ErrorAction SilentlyContinue |
Where-Object { $_.FullName -notmatch '\\\.git\\' } |
Select-Object -First 1
if ($scriptCands) {
Copy-Item $scriptCands.FullName $binScript -Force
Write-Host "Copied glib-genmarshal script to bin: $($scriptCands.FullName)"
return
}
# If we got here, nothing usable exists.
$nonExe = Get-ChildItem -Path $gtkPrefix -Recurse -File -Filter "glib-genmarshal*" -ErrorAction SilentlyContinue |
Where-Object { $_.Extension -notin @(".exe",".pdb",".txt") } |
Select-Object -First 20
if ($nonExe) {
Write-Host "Found non-.exe glib-genmarshal candidates:"
$nonExe | ForEach-Object { Write-Host " - $($_.FullName)" }
}
throw "GTK3 bundle extracted, but no usable glib-genmarshal was found under detected GTK prefix: $gtkPrefix"
}
$gtkPrefix = $null
$headers = @{
"User-Agent" = "zoitechat-ci"
"Authorization" = "Bearer $env:GITHUB_TOKEN"
"X-GitHub-Api-Version" = "2022-11-28"
}
try {
$release = Invoke-RestMethod -Headers $headers -Uri "https://api.github.com/repos/wingtk/gvsbuild/releases/latest"
$asset = $release.assets |
Where-Object { $_.name -imatch '^GTK3_Gvsbuild_.*_(x64|x86)\.zip$' -and $_.name -imatch "_$wantArch\.zip$" } |
Select-Object -First 1
if (-not $asset) {
Write-Host "Available assets:"
$release.assets | ForEach-Object { Write-Host " - $($_.name)" }
throw "Could not find a GTK3 $wantArch zip in wingtk/gvsbuild latest release."
}
$bundlePath = "deps\gtk3-$wantArch-bundle.zip"
Invoke-WebRequest $asset.browser_download_url -OutFile $bundlePath
Expand-Archive -Force $bundlePath -DestinationPath $extractRoot
$gtkPrefix = Pick-GtkPrefix -root $extractRoot
if (-not $gtkPrefix) {
throw "GTK3 bundle extracted, but could not locate any gtk-3*.lib to determine prefix."
}
Write-Host "Detected GTK prefix: $gtkPrefix"
} catch {
if ($wantArch -eq "x86") {
Write-Host "Wingtk GTK3 discovery failed for win32 (x86). Falling back to legacy GTK bundle."
$gtkPrefix = $null
} else {
throw
}
}
if (-not $gtkPrefix) {
Invoke-WebRequest https://github.com/zoitechat/gvsbuild/releases/download/zoitechat-2.17.0/gtk-${{ matrix.platform }}-2018-08-29-openssl1.1.7z -OutFile deps\gtk-${{ matrix.platform }}.7z
Ensure-Dir "C:\gtk-build\gtk"
& 7z.exe x deps\gtk-${{ matrix.platform }}.7z -oC:\gtk-build\gtk | Out-Null
} else {
$normBase = "C:\gtk-build\gtk\$wantPlat"
Ensure-Dir $normBase
$normRel = Join-Path $normBase "release"
if (Test-Path $normRel) { cmd /c rmdir /s /q "$normRel" | Out-Null }
New-Item -Path $normRel -ItemType Junction -Value $gtkPrefix | Out-Null
$gtkBin = Join-Path $normRel "bin"
$gtkLib = Join-Path $normRel "lib"
$gtkInc = Join-Path $normRel "include"
Ensure-Dir $gtkBin
Ensure-Dir $gtkLib
Ensure-Dir $gtkInc
Ensure-GenmarshalReady -gtkBinDir $gtkBin -gtkPrefix $gtkPrefix -wantPlat $wantPlat
Copy-AliasLib $gtkLib "gtk-win32-2.0.lib" @(
'^gtk-3\.0\.lib$',
'^gtk-3-0\.lib$',
'^gtk-3\.lib$',
'^gtk-3.*\.lib$'
)
Copy-AliasLib $gtkLib "ssleay32.lib" @('^libssl\.lib$', '^ssl\.lib$')
Copy-AliasLib $gtkLib "libeay32.lib" @('^libcrypto\.lib$', '^crypto\.lib$')
Copy-AliasLib $gtkLib "libxml2.lib" @('^libxml2\.lib$', '^libxml2-2\.0\.lib$', '^libxml2.*\.lib$')
}
# Resolve python root from setup-python
$pyRoot = $env:pythonLocation
if (-not $pyRoot) { $pyRoot = & python -c "import sys; print(sys.prefix)" }
foreach ($pyDir in @("C:\gtk-build\python-3.14.2", "C:\gtk-build\python-3.14")) {
New-Item -Path $pyDir -ItemType Directory -Force | Out-Null
$target = Join-Path $pyDir "${{ matrix.platform }}"
if (Test-Path $target) { Remove-Item $target -Recurse -Force }
New-Item -Path $pyDir -Name "${{ matrix.platform }}" -ItemType Junction -Value $pyRoot | Out-Null
}
python -m pip install --upgrade pip
python -m pip install cffi
- name: Build
shell: cmd
run: |
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\Tools\VsDevCmd.bat"
set "GTKROOT=C:\gtk-build\gtk\${{ matrix.platform }}\release"
set "PYTHON_DIR=C:\gtk-build\python-3.14.2\${{ matrix.platform }}"
if not exist "%PYTHON_DIR%\libs\python314.lib" (
echo Missing %PYTHON_DIR%\libs\python314.lib
dir "%PYTHON_DIR%\libs"
exit /b 1
)
if exist "%GTKROOT%\include" set "INCLUDE=%GTKROOT%\include;%INCLUDE%"
if exist "%GTKROOT%\lib" set "LIB=%GTKROOT%\lib;%LIB%"
set "LIB=%PYTHON_DIR%\libs;%LIB%"
set "INCLUDE=%PYTHON_DIR%\include;%INCLUDE%"
rem Fix Lua plugin build: include LuaJIT headers from repo if present
if exist "plugins\lua\luajit\src\lua.h" set "INCLUDE=%CD%\plugins\lua\luajit\src;%INCLUDE%"
if exist "plugins\lua\luajit-2.1\src\lua.h" set "INCLUDE=%CD%\plugins\lua\luajit-2.1\src;%INCLUDE%"
msbuild win32\zoitechat.sln /m /verbosity:minimal /p:Configuration=Release /p:Platform=${{ matrix.platform }}
- name: Preparing Artifacts
shell: cmd
run: |
move ..\zoitechat-build\${{ matrix.platform }}\ZoiteChat*.exe .\
move ..\zoitechat-build .\
- name: Upload Installer
id: upload_installer
uses: actions/upload-artifact@v4
with:
name: Installer ${{ matrix.arch }}
path: ZoiteChat*.exe
- name: Attest Installer (Artifact Attestation)
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
uses: actions/attest-build-provenance@v3
with:
subject-name: Installer ${{ matrix.arch }}
subject-digest: sha256:${{ steps.upload_installer.outputs.artifact-digest }}
- name: Upload Build Files
id: upload_buildfiles
uses: actions/upload-artifact@v4
with:
name: Build Files ${{ matrix.arch }}
path: zoitechat-build
- name: Attest Build Files (Artifact Attestation)
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository }}
uses: actions/attest-build-provenance@v3
with:
subject-name: Build Files ${{ matrix.arch }}
subject-digest: sha256:${{ steps.upload_buildfiles.outputs.artifact-digest }}