Files
zoitechat/.github/workflows/windows-build.yml
deepend-tildeclub 049d3c19af Add MSYS2 setup and improve dependency installation
Added MSYS2 setup step for GTK build fallback and updated the installation process for dependencies. Enhanced error handling and ensured proper paths for GTK and Lua headers.
2026-02-01 22:24:25 -07:00

378 lines
16 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
- name: Setup MSYS2 (for GTK build fallback)
uses: msys2/setup-msys2@v2
with:
msys2-location: C:\tools\msys64
update: true
install: >-
base-devel
git
unzip
p7zip
- 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
# Discover perl.exe and persist its bin dir.
$perlExe = Get-ChildItem -Path "C:\gtk-build\perl-5.20\${{ matrix.platform }}" -Recurse -File -Filter "perl.exe" | Select-Object -First 1
if (-not $perlExe) { throw "perl.exe not found under C:\gtk-build\perl-5.20\${{ matrix.platform }}" }
"PERL_BIN=$($perlExe.Directory.FullName)" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
function Find-GtkPrefix([string]$root) {
$gtkH = Get-ChildItem -Path $root -Recurse -File -Filter "gtk.h" |
Where-Object { $_.FullName -match '\\include\\gtk-3\.0\\gtk\\gtk\.h$' } |
Select-Object -First 1
if (-not $gtkH) { return $null }
return $gtkH.Directory.Parent.Parent.Parent.FullName
}
function Ensure-Junction([string]$linkPath, [string]$targetPath) {
if (Test-Path $linkPath) {
cmd /c rmdir /s /q "$linkPath" | Out-Null
}
New-Item -Path $linkPath -ItemType Junction -Value $targetPath | Out-Null
}
function Copy-AliasLib([string]$libDir, [string]$targetName, [string[]]$sourcePatterns) {
$target = Join-Path $libDir $targetName
if (Test-Path $target) { return }
$src = $null
foreach ($pat in $sourcePatterns) {
$src = Get-ChildItem -Path $libDir -File -Filter "*.lib" | Where-Object { $_.Name -match $pat } | 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 $libDir)"
}
}
function Ensure-GlibGenmarshalWrapper([string]$gtkBin) {
$wrapperPath = Join-Path $gtkBin "glib-genmarshal"
$exePath = Join-Path $gtkBin "glib-genmarshal.exe"
$realPath = Join-Path $gtkBin "glib-genmarshal.real"
if (Test-Path $realPath) { Remove-Item $realPath -Force }
if (Test-Path $wrapperPath) {
$first = (Get-Content -Path $wrapperPath -TotalCount 1 -ErrorAction SilentlyContinue)
if ($first -notmatch 'import os') {
Move-Item $wrapperPath $realPath -Force
}
}
if (-not (Test-Path $exePath) -and -not (Test-Path $realPath)) {
$cand = Get-ChildItem -Path $gtkBin -File -Filter "glib-genmarshal*" | Select-Object -First 1
if ($cand) {
Move-Item $cand.FullName $realPath -Force
}
}
if (-not (Test-Path $exePath) -and -not (Test-Path $realPath)) {
throw "GTK3 prefix present, but no glib-genmarshal tool found under $gtkBin"
}
$wrapper = @(
"import os, subprocess, sys",
"base = os.path.dirname(__file__)",
"exe = os.path.join(base, 'glib-genmarshal.exe')",
"real = os.path.join(base, 'glib-genmarshal.real')",
"tool = exe if os.path.exists(exe) else real",
"if not os.path.exists(tool):",
" sys.stderr.write('glib-genmarshal tool not found\\n')",
" sys.exit(1)",
"if tool.lower().endswith('.exe'):",
" sys.exit(subprocess.call([tool] + sys.argv[1:]))",
"first = b''",
"try:",
" with open(tool, 'rb') as f: first = f.readline().lower()",
"except Exception:",
" pass",
"if b'perl' in first:",
" sys.exit(subprocess.call(['perl', tool] + sys.argv[1:]))",
"sys.exit(subprocess.call([sys.executable, tool] + sys.argv[1:]))"
)
$wrapper | Set-Content -Path $wrapperPath -Encoding ASCII
}
function Ensure-LuaHeaders([string]$gtkInc) {
# Put headers in BOTH include\ and include\lua\ to satisfy either include style.
$luaDir = Join-Path $gtkInc "lua"
New-Item -Path $luaDir -ItemType Directory -Force | Out-Null
$zip = "deps\luajit-v2.1.zip"
$dst = "deps\luajit-src"
if (Test-Path $dst) { Remove-Item $dst -Recurse -Force }
New-Item -Path $dst -ItemType Directory -Force | Out-Null
Invoke-WebRequest https://github.com/LuaJIT/LuaJIT/archive/refs/heads/v2.1.zip -OutFile $zip
Expand-Archive -Force $zip -DestinationPath $dst
$srcDir = Get-ChildItem -Path $dst -Directory | Select-Object -First 1
if (-not $srcDir) { throw "LuaJIT source zip extracted, but no top directory found." }
$hdrRoot = Join-Path $srcDir.FullName "src"
foreach ($h in @("lua.h", "lualib.h", "lauxlib.h", "luaconf.h")) {
$p = Join-Path $hdrRoot $h
if (-not (Test-Path $p)) { throw "LuaJIT header missing: $p" }
Copy-Item $p (Join-Path $gtkInc $h) -Force
Copy-Item $p (Join-Path $luaDir $h) -Force
}
if (-not (Test-Path (Join-Path $gtkInc "lua.h"))) { throw "lua.h was not installed into $gtkInc" }
if (-not (Test-Path (Join-Path $luaDir "lua.h"))) { throw "lua.h was not installed into $luaDir" }
}
# ------------------------------------------
# GTK: Prefer prebuilt wingtk/gvsbuild GTK3 bundles.
# If missing for x86 (win32), build GTK3 via gvsbuild from source.
# ------------------------------------------
$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
$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 -match '^GTK3_Gvsbuild_.*_(x64|x86)\.zip$' -or $_.name -match '(?i)gtk3.*(x64|x86)\.zip$') -and
$_.name -match "_$wantArch\.zip$"
} |
Select-Object -First 1
if ($asset) {
$bundlePath = "deps\gtk3-$wantArch-bundle.zip"
Invoke-WebRequest $asset.browser_download_url -OutFile $bundlePath
Expand-Archive -Force $bundlePath -DestinationPath $extractRoot
$gtkPrefix = Find-GtkPrefix $extractRoot
if (-not $gtkPrefix) { throw "GTK3 bundle extracted, but gtk.h not found. Layout unexpected." }
Write-Host "Detected GTK prefix: $gtkPrefix"
} else {
Write-Host "No prebuilt GTK3 bundle found for $wantArch. Will build GTK3 from source via gvsbuild."
}
} catch {
Write-Host "Prebuilt GTK3 discovery failed: $($_.Exception.Message)"
Write-Host "Will build GTK3 from source via gvsbuild."
}
if (-not $gtkPrefix) {
$gvsDir = "C:\gtk-build\github\gvsbuild"
if (-not (Test-Path $gvsDir)) {
New-Item -Path "C:\gtk-build\github" -ItemType Directory -Force | Out-Null
git clone --depth 1 https://github.com/wingtk/gvsbuild.git $gvsDir
}
Push-Location $gvsDir
python -m pip install --upgrade pip
python -m pip install -r requirements.txt
python .\build.py build -p=$wantArch --vs-ver=16 --msys-dir=C:\tools\msys64 --gtk3-ver=3.24 -c=release gtk3
Pop-Location
$gtkPrefix = "C:\gtk-build\gtk\$wantArch\release"
if (-not (Test-Path (Join-Path $gtkPrefix "include\gtk-3.0\gtk\gtk.h"))) {
throw "gvsbuild finished, but expected GTK headers were not found under $gtkPrefix"
}
}
# Normalize GTK location to the path your solution expects:
$normBase = "C:\gtk-build\gtk\$wantPlat"
New-Item -Path $normBase -ItemType Directory -Force | Out-Null
$normRel = Join-Path $normBase "release"
Ensure-Junction $normRel $gtkPrefix
$gtkBin = Join-Path $normRel "bin"
$gtkLib = Join-Path $normRel "lib"
$gtkInc = Join-Path $normRel "include"
if (-not (Test-Path $gtkBin)) { throw "GTK bin dir missing at $gtkBin" }
if (-not (Test-Path $gtkLib)) { throw "GTK lib dir missing at $gtkLib" }
if (-not (Test-Path $gtkInc)) { throw "GTK include dir missing at $gtkInc" }
Ensure-GlibGenmarshalWrapper $gtkBin
Ensure-LuaHeaders $gtkInc
# Expected import libs (some projects hardcode these exact names)
Copy-AliasLib $gtkLib "gtk-3.0.lib" @('^gtk-3-0\.lib$', '^gtk-3\.lib$', '^gtk-3.*\.lib$', '^gtk-win32-2\.0\.lib$')
Copy-AliasLib $gtkLib "gdk-3.0.lib" @('^gdk-3-0\.lib$', '^gdk-3\.lib$', '^gdk-3.*\.lib$')
Copy-AliasLib $gtkLib "gdk-win32-3.0.lib" @('^gdk-win32-3-0\.lib$', '^gdk-win32-3\.lib$', '^gdk-win32-3.*\.lib$')
# Legacy GTK2 name expected by older vcxproj (keep it)
Copy-AliasLib $gtkLib "gtk-win32-2.0.lib" @('^gtk-3\.0\.lib$', '^gtk-3-0\.lib$', '^gtk-3\.lib$', '^gtk-3.*\.lib$')
# OpenSSL legacy names (keep older projects happy if referenced)
Copy-AliasLib $gtkLib "ssleay32.lib" @('^libssl\.lib$', '^ssl\.lib$')
Copy-AliasLib $gtkLib "libeay32.lib" @('^libcrypto\.lib$', '^crypto\.lib$')
# libxml2 legacy name
Copy-AliasLib $gtkLib "libxml2.lib" @('^libxml2.*\.lib$')
# Persist GTK root for later steps.
"GTK_ROOT=$normRel" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
# Resolve python root from setup-python
$pyRoot = $env:pythonLocation
if (-not $pyRoot) { $pyRoot = & python -c "import sys; print(sys.prefix)" }
# Create BOTH paths because the .vcxproj hard-codes python-3.14\...
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=%GTK_ROOT%"
if not exist "%GTKROOT%\include\gtk-3.0\gtk\gtk.h" (
echo Missing GTK headers under %GTKROOT%
dir "%GTKROOT%\include\gtk-3.0\gtk"
exit /b 1
)
if not exist "%GTKROOT%\include\lua.h" (
echo Missing lua.h under %GTKROOT%\include
dir "%GTKROOT%\include"
)
if not exist "%GTKROOT%\include\lua\lua.h" (
echo Missing lua\lua.h under %GTKROOT%\include\lua
dir "%GTKROOT%\include\lua"
)
set "PATH=%PERL_BIN%;%GTKROOT%\bin;%PATH%"
set "LIB=%GTKROOT%\lib;%LIB%"
set "INCLUDE=%GTKROOT%\include\lua;%GTKROOT%\include;%INCLUDE%"
set "CL=/I""%GTKROOT%\include"" /I""%GTKROOT%\include\lua"" %CL%"
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
)
set "LIB=%PYTHON_DIR%\libs;%LIB%"
set "INCLUDE=%PYTHON_DIR%\include;%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 }}