Files
zoitechat/.github/workflows/windows-build.yml
deepend-tildeclub 069c6a3f81 Implement alias handling for DLLs and headers
Added functions to copy DLL and header aliases for Enchant2 and other libraries, ensuring they are present in the specified directories.
2026-02-02 00:06:24 -07:00

832 lines
36 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
# MSYS2 is required for the gvsbuild-from-source fallback path.
- 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 # ...\include\gtk-3.0\gtk\gtk.h -> prefix
}
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 Copy-AliasDll([string]$binDir, [string]$targetName, [string[]]$sourcePatterns) {
$target = Join-Path $binDir $targetName
if (Test-Path $target) { return }
$src = $null
foreach ($pat in $sourcePatterns) {
$src = Get-ChildItem -Path $binDir -File -Filter "*.dll" | Where-Object { $_.Name -match $pat } | Select-Object -First 1
if ($src) { break }
}
if ($src) {
Copy-Item $src.FullName $target -Force
Write-Host "DLL Alias: $targetName <= $($src.Name)"
} else {
Write-Host "DLL alias not created: $targetName (no match in $binDir)"
}
}
function Ensure-HeaderAlias([string]$incRoot, [string]$targetName, [string[]]$relativeCandidates) {
$target = Join-Path $incRoot $targetName
if (Test-Path $target) { return }
foreach ($rel in $relativeCandidates) {
$src = Join-Path $incRoot $rel
if (Test-Path $src) {
Copy-Item $src $target -Force
Write-Host "Header Alias: $targetName <= $rel"
return
}
}
Write-Host "Header alias not created: $targetName (no candidate found under $incRoot)"
}
function Ensure-GlibGenmarshalWrapper([string]$gtkBin) {
$wrapperPath = Join-Path $gtkBin "glib-genmarshal"
$exePath = Join-Path $gtkBin "glib-genmarshal.exe"
# If a non-exe tool exists, move it aside so our wrapper name is stable.
$realPath = Join-Path $gtkBin "glib-genmarshal.real"
if (Test-Path $realPath) { Remove-Item $realPath -Force }
if (Test-Path $wrapperPath) {
# If it's already our wrapper, keep it. Otherwise move to .real.
$first = (Get-Content -Path $wrapperPath -TotalCount 1 -ErrorAction SilentlyContinue)
if ($first -notmatch 'import os') {
Move-Item $wrapperPath $realPath -Force
}
}
# If no exe and no real tool, search for any glib-genmarshal* in bin and use that.
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"
}
# Write a python wrapper that dispatches to:
# - glib-genmarshal.exe if present
# - otherwise glib-genmarshal.real (perl or python script)
$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-LuaJit([string]$gtkInc, [string]$gtkLib, [string]$gtkBin, [string]$platform) {
# Provide <lua.h> et al + lua51.lib/dll for the lua plugin build.
$needHeaders = -not (Test-Path (Join-Path $gtkInc "lua.h"))
$needLib = -not (Test-Path (Join-Path $gtkLib "lua51.lib"))
if (-not $needHeaders -and -not $needLib) {
return
}
$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
$topDir = Get-ChildItem -Path $dst -Directory | Select-Object -First 1
if (-not $topDir) { throw "LuaJIT source zip extracted, but no top directory found." }
$srcDir = Join-Path $topDir.FullName "src"
foreach ($h in @("lua.h", "lualib.h", "lauxlib.h", "luaconf.h")) {
$p = Join-Path $srcDir $h
if (-not (Test-Path $p)) { throw "LuaJIT header missing: $p" }
}
# Copy headers into the common include layouts used by various projects.
$incTargets = @(
$gtkInc,
(Join-Path $gtkInc "luajit-2.1"),
(Join-Path $gtkInc "lua5.1"),
(Join-Path $gtkInc "lua")
)
foreach ($t in $incTargets) {
if (-not (Test-Path $t)) { New-Item -Path $t -ItemType Directory -Force | Out-Null }
foreach ($h in @("lua.h", "lualib.h", "lauxlib.h", "luaconf.h")) {
Copy-Item (Join-Path $srcDir $h) (Join-Path $t $h) -Force
}
}
# Build lua51.lib/dll with MSVC if not present.
if (-not (Test-Path (Join-Path $gtkLib "lua51.lib"))) {
$vsDevCmd = "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\Tools\VsDevCmd.bat"
if (-not (Test-Path $vsDevCmd)) { throw "VsDevCmd.bat not found at $vsDevCmd" }
$arch = if ($platform -eq "x64") { "amd64" } else { "x86" }
$cmd = @(
"""$vsDevCmd"" -no_logo -arch=$arch -host_arch=$arch",
"cd /d ""$srcDir""",
"call msvcbuild.bat"
) -join " && "
cmd /c $cmd | Out-Host
$dll = Join-Path $srcDir "lua51.dll"
$lib = Join-Path $srcDir "lua51.lib"
if (-not (Test-Path $dll) -or -not (Test-Path $lib)) {
throw "LuaJIT build finished, but lua51.dll/lib not found under $srcDir"
}
Copy-Item $dll (Join-Path $gtkBin "lua51.dll") -Force
Copy-Item $lib (Join-Path $gtkLib "lua51.lib") -Force
}
# Provide common import-lib aliases some projects expect.
Copy-AliasLib $gtkLib "lua.lib" @('^lua51\.lib$')
Copy-AliasLib $gtkLib "luajit.lib" @('^lua51\.lib$')
# Sanity check: ensure a bare <lua.h> include will work somewhere.
if (-not (Test-Path (Join-Path $gtkInc "lua.h"))) {
throw "lua.h still missing under $gtkInc after installation"
}
}
function Ensure-Zlib([string]$gtkInc, [string]$gtkLib, [string]$platform) {
# Projects explicitly link "zlib.lib". Provide it.
$want = Join-Path $gtkLib "zlib.lib"
if (Test-Path $want) { return }
# Try common alternative names first (bundles vary).
Copy-AliasLib $gtkLib "zlib.lib" @(
'^zlib1\.lib$',
'^zdll\.lib$',
'^zlibstatic\.lib$',
'^zlibwapi\.lib$',
'^zlib.*\.lib$'
)
if (Test-Path $want) { return }
# If still missing, build zlib with MSVC (fast) and drop zlib.lib into GTK lib.
$zip = "deps\zlib-1.3.1.zip"
$dst = "deps\zlib-src"
if (Test-Path $dst) { Remove-Item $dst -Recurse -Force }
New-Item -Path $dst -ItemType Directory -Force | Out-Null
Invoke-WebRequest https://github.com/madler/zlib/archive/refs/tags/v1.3.1.zip -OutFile $zip
Expand-Archive -Force $zip -DestinationPath $dst
$topDir = Get-ChildItem -Path $dst -Directory | Select-Object -First 1
if (-not $topDir) { throw "zlib source zip extracted, but no top directory found." }
$srcDir = $topDir.FullName
$vsDevCmd = "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\Tools\VsDevCmd.bat"
if (-not (Test-Path $vsDevCmd)) { throw "VsDevCmd.bat not found at $vsDevCmd" }
$arch = if ($platform -eq "x64") { "amd64" } else { "x86" }
$cmd = @(
"""$vsDevCmd"" -no_logo -arch=$arch -host_arch=$arch",
"cd /d ""$srcDir""",
"nmake -f win32\\Makefile.msc clean",
"nmake -f win32\\Makefile.msc"
) -join " && "
cmd /c $cmd | Out-Host
$builtLib = Join-Path $srcDir "zlib.lib"
if (-not (Test-Path $builtLib)) {
# Some environments produce import lib name; try to alias those too.
Copy-AliasLib (Join-Path $srcDir ".") "zlib.lib" @('^zdll\.lib$','^zlib.*\.lib$')
if (-not (Test-Path $builtLib)) {
throw "zlib build finished, but zlib.lib was not produced under $srcDir"
}
}
Copy-Item $builtLib $want -Force
Write-Host "Installed zlib.lib => $want"
# Ensure headers exist where the solution expects them (usually already in GTK).
foreach ($h in @("zlib.h", "zconf.h")) {
$dstH = Join-Path $gtkInc $h
if (-not (Test-Path $dstH)) {
$srcH = Join-Path $srcDir $h
if (Test-Path $srcH) {
Copy-Item $srcH $dstH -Force
Write-Host "Installed header $h => $dstH"
}
}
}
}
function Ensure-LibXml2([string]$gtkLib, [string]$gtkBin, [string]$platform) {
# Projects explicitly link "libxml2.lib". Provide it.
$want = Join-Path $gtkLib "libxml2.lib"
if (Test-Path $want) { return }
# First: alias from common names shipped by different bundles.
Copy-AliasLib $gtkLib "libxml2.lib" @(
'^libxml2-2\.0\.lib$',
'^libxml2-2\.lib$',
'^libxml2_a\.lib$',
'^libxml2-static\.lib$',
'^xml2\.lib$',
'^libxml2.*\.lib$'
)
if (Test-Path $want) { return }
# Second: if only a DLL exists, generate an import library from it (gendef + lib.exe).
$dll = Get-ChildItem -Path $gtkBin -File -Filter "libxml2*.dll" | Sort-Object -Property Name | Select-Object -First 1
if (-not $dll) {
$dll = Get-ChildItem -Path $gtkBin -File -Filter "xml2*.dll" | Sort-Object -Property Name | Select-Object -First 1
}
if (-not $dll) {
throw "libxml2.lib missing and no libxml2 DLL found under $gtkBin"
}
$gendefExe = Get-ChildItem -Path "C:\gtk-build" -Recurse -File -Filter "gendef.exe" | Select-Object -First 1
if (-not $gendefExe) {
throw "gendef.exe not found under C:\gtk-build (gendef extraction failed?)"
}
$defPath = Join-Path $env:TEMP "libxml2.def"
$tmpLib = Join-Path $env:TEMP "libxml2.lib"
& $gendefExe.FullName $dll.FullName | Out-File -FilePath $defPath -Encoding ascii
if (-not (Test-Path $defPath) -or (Get-Item $defPath).Length -lt 64) {
throw "gendef produced an invalid/empty DEF for $($dll.Name)"
}
$vsDevCmd = "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\Tools\VsDevCmd.bat"
if (-not (Test-Path $vsDevCmd)) { throw "VsDevCmd.bat not found at $vsDevCmd" }
$vcArch = if ($platform -eq "x64") { "amd64" } else { "x86" }
$machineOpt = if ($platform -eq "x64") { "X64" } else { "X86" }
$cmd = @(
"""$vsDevCmd"" -no_logo -arch=$vcArch -host_arch=$vcArch",
"lib /nologo /def:""$defPath"" /machine:$machineOpt /out:""$tmpLib"""
) -join " && "
cmd /c $cmd | Out-Host
if (-not (Test-Path $tmpLib)) {
throw "Failed to generate libxml2.lib from $($dll.Name)"
}
Copy-Item $tmpLib $want -Force
Write-Host "Generated libxml2.lib from $($dll.Name) => $want"
}
function Ensure-LibJpeg([string]$gtkLib, [string]$gtkBin, [string]$platform) {
# Projects explicitly link "libjpeg.lib". Provide it.
$want = Join-Path $gtkLib "libjpeg.lib"
if (Test-Path $want) { return }
# First: alias from common names shipped by different bundles.
# Prefer the libjpeg ABI libs; avoid turbojpeg.lib unless you really mean that API.
Copy-AliasLib $gtkLib "libjpeg.lib" @(
'^jpeg\.lib$',
'^libjpeg-8\.lib$',
'^libjpeg-9\.lib$',
'^libjpeg-7\.lib$',
'^libjpeg-6\.lib$',
'^libjpeg_a\.lib$',
'^libjpeg-static\.lib$',
'^libjpeg.*\.lib$'
)
if (Test-Path $want) { return }
# Second: if only a DLL exists, generate an import library from it (gendef + lib.exe).
$dll = Get-ChildItem -Path $gtkBin -File -Filter "libjpeg*.dll" | Sort-Object -Property Name | Select-Object -First 1
if (-not $dll) {
$dll = Get-ChildItem -Path $gtkBin -File -Filter "jpeg*.dll" | Sort-Object -Property Name | Select-Object -First 1
}
if (-not $dll) {
throw "libjpeg.lib missing and no JPEG DLL found under $gtkBin"
}
$gendefExe = Get-ChildItem -Path "C:\gtk-build" -Recurse -File -Filter "gendef.exe" | Select-Object -First 1
if (-not $gendefExe) {
throw "gendef.exe not found under C:\gtk-build (gendef extraction failed?)"
}
$defPath = Join-Path $env:TEMP "libjpeg.def"
$tmpLib = Join-Path $env:TEMP "libjpeg.lib"
& $gendefExe.FullName $dll.FullName | Out-File -FilePath $defPath -Encoding ascii
if (-not (Test-Path $defPath) -or (Get-Item $defPath).Length -lt 64) {
throw "gendef produced an invalid/empty DEF for $($dll.Name)"
}
$vsDevCmd = "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\Tools\VsDevCmd.bat"
if (-not (Test-Path $vsDevCmd)) { throw "VsDevCmd.bat not found at $vsDevCmd" }
$vcArch = if ($platform -eq "x64") { "amd64" } else { "x86" }
$machineOpt = if ($platform -eq "x64") { "X64" } else { "X86" }
$cmd = @(
"""$vsDevCmd"" -no_logo -arch=$vcArch -host_arch=$vcArch",
"lib /nologo /def:""$defPath"" /machine:$machineOpt /out:""$tmpLib"""
) -join " && "
cmd /c $cmd | Out-Host
if (-not (Test-Path $tmpLib)) {
throw "Failed to generate libjpeg.lib from $($dll.Name)"
}
Copy-Item $tmpLib $want -Force
Write-Host "Generated libjpeg.lib from $($dll.Name) => $want"
}
function Ensure-Enchant2([string]$gtkPrefix, [string]$wantArch) {
$inc = Join-Path $gtkPrefix "include"
$bin = Join-Path $gtkPrefix "bin"
$lib = Join-Path $gtkPrefix "lib"
$hasHeader = (Test-Path (Join-Path $inc "enchant-2\enchant-provider.h")) -or (Test-Path (Join-Path $inc "enchant-provider.h"))
$hasDll = (Get-ChildItem -Path $bin -File -Filter "libenchant*.dll" -ErrorAction SilentlyContinue | Select-Object -First 1) -ne $null
$hasLib = (Get-ChildItem -Path $lib -File -Filter "libenchant*.lib" -ErrorAction SilentlyContinue | Select-Object -First 1) -ne $null
if ($hasHeader -and $hasDll -and $hasLib) { return }
Write-Host "Enchant2 not fully present in GTK prefix; attempting to build via gvsbuild and merge..."
$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
$built = $false
foreach ($mod in @("enchant", "enchant2")) {
try {
Write-Host "Trying gvsbuild module: $mod"
python .\build.py build -p=$wantArch --vs-ver=16 --msys-dir=C:\tools\msys64 -c=release $mod
$built = $true
break
} catch {
Write-Host "Module $mod failed: $($_.Exception.Message)"
}
}
Pop-Location
if (-not $built) {
throw "Could not build Enchant via gvsbuild (tried: enchant, enchant2)."
}
$srcPrefix = "C:\gtk-build\gtk\$wantArch\release"
if (-not (Test-Path $srcPrefix)) { throw "Expected gvsbuild prefix not found: $srcPrefix" }
# Merge headers
if (Test-Path (Join-Path $srcPrefix "include\enchant-2")) {
New-Item -Path (Join-Path $inc "enchant-2") -ItemType Directory -Force | Out-Null
Copy-Item (Join-Path $srcPrefix "include\enchant-2\*") (Join-Path $inc "enchant-2") -Recurse -Force
}
# Merge libs/dlls
Copy-Item (Join-Path $srcPrefix "bin\libenchant*.dll") $bin -Force -ErrorAction SilentlyContinue
Copy-Item (Join-Path $srcPrefix "lib\libenchant*.lib") $lib -Force -ErrorAction SilentlyContinue
# Re-evaluate
$hasHeader = (Test-Path (Join-Path $inc "enchant-2\enchant-provider.h")) -or (Test-Path (Join-Path $inc "enchant-provider.h"))
$hasDll = (Get-ChildItem -Path $bin -File -Filter "libenchant*.dll" -ErrorAction SilentlyContinue | Select-Object -First 1) -ne $null
$hasLib = (Get-ChildItem -Path $lib -File -Filter "libenchant*.lib" -ErrorAction SilentlyContinue | Select-Object -First 1) -ne $null
if (-not ($hasHeader -and $hasDll -and $hasLib)) {
throw "Enchant2 still missing after gvsbuild merge. Header/DLL/LIB present? $hasHeader / $hasDll / $hasLib"
}
Write-Host "Enchant2 merged into GTK prefix."
}
# ------------------------------------------
# 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" } # gvsbuild uses x64/x86
$wantPlat = "${{ matrix.platform }}" # solution uses x64/win32
$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"
# Try a couple of patterns in case naming shifts slightly.
$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) {
# Build GTK3 via gvsbuild (VS2019) as a fallback.
$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
# Build GTK3 (3.24) for VS2019 (16), using MSYS2.
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:
# C:\gtk-build\gtk\<platform>\release\...
$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" }
# glib-genmarshal: vcxproj calls python.exe <path>\glib-genmarshal (no extension).
# Ensure we always have a python wrapper named glib-genmarshal in GTK\bin.
Ensure-GlibGenmarshalWrapper $gtkBin
# Provide LuaJIT headers + import lib for the lua plugin.
Ensure-LuaJit $gtkInc $gtkLib $gtkBin $wantPlat
# Ensure zlib.lib exists (some bundles name it differently or omit it).
Ensure-Zlib $gtkInc $gtkLib $wantPlat
# Ensure libxml2.lib exists (some bundles ship only DLL or different .lib name).
Ensure-LibXml2 $gtkLib $gtkBin $wantPlat
# Ensure libjpeg.lib exists (some bundles ship jpeg.lib / libjpeg-8.lib or only DLL).
Ensure-LibJpeg $gtkLib $gtkBin $wantPlat
# Ensure Enchant2 (needed by libenchant_win8 project + packaging expectations).
Ensure-Enchant2 $normRel $wantArch
# Header aliases for projects that include flat names (quotes) instead of subdir includes.
Ensure-HeaderAlias $gtkInc "enchant-provider.h" @("enchant-2\enchant-provider.h", "enchant\enchant-provider.h")
Ensure-HeaderAlias $gtkInc "hb.h" @("harfbuzz\hb.h")
Ensure-HeaderAlias $gtkInc "hb-ot.h" @("harfbuzz\hb-ot.h")
# DLL aliases to satisfy win32\copy\copy.vcxproj (it expects older names).
Copy-AliasDll $gtkBin "freetype.dll" @('^freetype\.dll$', '^libfreetype-6\.dll$', '^libfreetype.*\.dll$')
Copy-AliasDll $gtkBin "fontconfig.dll" @('^fontconfig\.dll$', '^libfontconfig-1\.dll$', '^libfontconfig.*\.dll$')
Copy-AliasDll $gtkBin "gdk-win32-2.0.dll" @('^gdk-3-0\.dll$', '^libgdk-3-0\.dll$', '^gdk-3.*\.dll$')
Copy-AliasDll $gtkBin "gtk-win32-2.0.dll" @('^gtk-3-0\.dll$', '^libgtk-3-0\.dll$', '^gtk-3.*\.dll$')
Copy-AliasDll $gtkBin "libenchant.dll" @('^libenchant-2\.dll$', '^libenchant-2-2\.dll$', '^libenchant.*\.dll$')
Copy-AliasDll $gtkBin "ffi-7.dll" @('^ffi-7\.dll$', '^libffi-7\.dll$', '^libffi-8\.dll$', '^ffi-8\.dll$', '^libffi-.*\.dll$')
Copy-AliasDll $gtkBin "libxml2.dll" @('^libxml2\.dll$', '^libxml2-2\.dll$', '^libxml2-2-2\.dll$', '^xml2\.dll$', '^libxml2.*\.dll$')
Copy-AliasDll $gtkBin "cairo.dll" @('^cairo\.dll$', '^libcairo-2\.dll$', '^libcairo.*\.dll$')
# Compatibility aliases for differing .lib names across bundles.
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 "gtk-win32-2.0.lib" @(
'^gtk-3\.0\.lib$',
'^gtk-3-0\.lib$',
'^gtk-3\.lib$',
'^gtk-3.*\.lib$'
)
# gdk: project expects gdk-3.0.lib but bundles often ship gdk-3.lib / gdk-3-0.lib
Copy-AliasLib $gtkLib "gdk-3.0.lib" @(
'^gdk-3-0\.lib$',
'^gdk-3\.lib$',
'^gdk-3.*\.lib$',
'^gdk-win32-2\.0\.lib$'
)
Copy-AliasLib $gtkLib "gdk-win32-2.0.lib" @(
'^gdk-3\.0\.lib$',
'^gdk-3-0\.lib$',
'^gdk-3\.lib$',
'^gdk-3.*\.lib$'
)
# OpenSSL legacy names (keep older projects happy if they still reference these)
Copy-AliasLib $gtkLib "ssleay32.lib" @('^libssl\.lib$', '^ssl\.lib$')
Copy-AliasLib $gtkLib "libeay32.lib" @('^libcrypto\.lib$', '^crypto\.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%\lib\zlib.lib" (
echo Missing zlib.lib under %GTKROOT%\lib
dir "%GTKROOT%\lib"
exit /b 1
)
if not exist "%GTKROOT%\lib\libxml2.lib" (
echo Missing libxml2.lib under %GTKROOT%\lib
dir "%GTKROOT%\lib"
dir "%GTKROOT%\bin"
exit /b 1
)
if not exist "%GTKROOT%\lib\libjpeg.lib" (
echo Missing libjpeg.lib under %GTKROOT%\lib
dir "%GTKROOT%\lib"
dir "%GTKROOT%\bin"
exit /b 1
)
rem Make MSVC find hb.h and enchant-provider.h (they live in subdirs under include\)
set "INCLUDE=%GTKROOT%\include\harfbuzz;%GTKROOT%\include\enchant-2;%GTKROOT%\include\freetype2;%INCLUDE%"
rem Sanity: these are expected by win32\copy\copy.vcxproj
if not exist "%GTKROOT%\bin\freetype.dll" (
echo Missing freetype.dll under %GTKROOT%\bin
dir "%GTKROOT%\bin"
exit /b 1
)
if not exist "%GTKROOT%\bin\fontconfig.dll" (
echo Missing fontconfig.dll under %GTKROOT%\bin
dir "%GTKROOT%\bin"
exit /b 1
)
if not exist "%GTKROOT%\bin\gdk-win32-2.0.dll" (
echo Missing gdk-win32-2.0.dll under %GTKROOT%\bin
dir "%GTKROOT%\bin"
exit /b 1
)
if not exist "%GTKROOT%\bin\gtk-win32-2.0.dll" (
echo Missing gtk-win32-2.0.dll under %GTKROOT%\bin
dir "%GTKROOT%\bin"
exit /b 1
)
if not exist "%GTKROOT%\bin\libenchant.dll" (
echo Missing libenchant.dll under %GTKROOT%\bin
dir "%GTKROOT%\bin"
exit /b 1
)
if not exist "%GTKROOT%\bin\ffi-7.dll" (
echo Missing ffi-7.dll under %GTKROOT%\bin
dir "%GTKROOT%\bin"
exit /b 1
)
if not exist "%GTKROOT%\bin\libxml2.dll" (
echo Missing libxml2.dll under %GTKROOT%\bin
dir "%GTKROOT%\bin"
exit /b 1
)
if not exist "%GTKROOT%\bin\cairo.dll" (
echo Missing cairo.dll under %GTKROOT%\bin
dir "%GTKROOT%\bin"
exit /b 1
)
set "PATH=%PERL_BIN%;%GTKROOT%\bin;%PATH%"
set "LIB=%GTKROOT%\lib;%LIB%"
set "INCLUDE=%GTKROOT%\include;%INCLUDE%"
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 }}