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\\release\{bin,include,lib} # # IMPORTANT: do NOT assume layout inside the zip. Detect prefix by locating gtk-3*.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) { # Returns 0x8664 (x64), 0x014c (x86), or $null if not PE. 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 Pick-GtkPrefix([string]$root, [string]$arch) { # Find a plausible GTK3 MSVC import lib anywhere in extracted tree. $libs = Get-ChildItem -Path $root -Recurse -File -Filter "*.lib" -ErrorAction SilentlyContinue # Prefer exact well-known names first. $preferred = @( '^gtk-3\.0\.lib$', '^gtk-3-0\.lib$', '^gtk-3\.lib$' ) foreach ($rx in $preferred) { $cand = $libs | Where-Object { $_.Name -match $rx } | Sort-Object -Property FullName | Select-Object -First 1 if ($cand) { # prefix is parent of the lib directory return (Split-Path -Parent $cand.Directory.FullName) } } # Fallback: any gtk-3*.lib $cand2 = $libs | Where-Object { $_.Name -match '^gtk-3.*\.lib$' } | Sort-Object -Property FullName | Select-Object -First 1 if ($cand2) { return (Split-Path -Parent $cand2.Directory.FullName) } return $null } function Ensure-Dir([string]$p) { if (-not (Test-Path $p)) { New-Item -Path $p -ItemType Directory -Force | Out-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-GenmarshalWrapper([string]$gtkBinDir, [string]$gtkPrefix, [string]$wantPlat) { Ensure-Dir $gtkBinDir # Search *anywhere* under prefix for glib-genmarshal*.exe (layout varies by bundle) $cands = Get-ChildItem -Path $gtkPrefix -Recurse -File -Filter "glib-genmarshal*.exe" -ErrorAction SilentlyContinue if (-not $cands -or $cands.Count -eq 0) { # Also try without extension, just in case the bundle ships a script $cands2 = Get-ChildItem -Path $gtkPrefix -Recurse -File -Filter "glib-genmarshal*" -ErrorAction SilentlyContinue | Where-Object { $_.Extension -ne ".pdb" -and $_.Extension -ne ".txt" } if ($cands2 -and $cands2.Count -gt 0) { Write-Host "Found non-.exe glib-genmarshal candidates:" $cands2 | Select-Object -First 20 | ForEach-Object { Write-Host " - $($_.FullName)" } } throw "GTK3 bundle extracted, but no glib-genmarshal*.exe found anywhere under detected GTK prefix: $gtkPrefix" } $wantMachine = if ($wantPlat -eq "x64") { 0x8664 } else { 0x014c } # Prefer machine-matching candidates. $picked = $null foreach ($c in $cands) { $m = Get-PEMachine $c.FullName if ($m -eq $wantMachine) { $picked = $c; break } } if (-not $picked) { # Last resort: just take the first one and hope it's not the wrong arch. $picked = $cands | Select-Object -First 1 Write-Host "Warning: could not confirm glib-genmarshal arch; using: $($picked.FullName)" } else { Write-Host "glib-genmarshal selected: $($picked.FullName)" } # The vcxproj calls: # python.exe "...\bin\glib-genmarshal" ... # So create a python script *without extension* at that exact path. $genTarget = Join-Path $gtkBinDir "glib-genmarshal" $wrapperLines = @( "import os, subprocess, sys", "tool = os.path.join(os.path.dirname(__file__), r'$($picked.Name)')", "sys.exit(subprocess.call([tool] + sys.argv[1:]))" ) $wrapperLines | Set-Content -Path $genTarget -Encoding ASCII # Also ensure the actual exe is reachable from bin (copy if it lives elsewhere) $exeInBin = Join-Path $gtkBinDir $picked.Name if (-not (Test-Path $exeInBin)) { Copy-Item $picked.FullName $exeInBin -Force } } $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" # Your logs show assets like: # GTK3_Gvsbuild_2026.1.0_x64.zip # GTK3_Gvsbuild_2026.1.0_x64.zip (etc) $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)" } if ($wantArch -eq "x86") { Write-Host "No GTK3 x86 asset found in wingtk/gvsbuild latest release; win32 build cannot use wingtk bundle." } 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 -arch $wantArch if (-not $gtkPrefix) { Write-Host "Dumping a few .lib filenames found (for debugging):" Get-ChildItem -Path $extractRoot -Recurse -File -Filter "*.lib" -ErrorAction SilentlyContinue | Select-Object -First 50 | ForEach-Object { Write-Host " - $($_.FullName)" } 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 (may not be GTK3)." $gtkPrefix = $null } else { throw } } if (-not $gtkPrefix) { # Legacy bundle path (your original) as last resort for win32 to keep workflow moving. 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 { # Normalize GTK location to the path your solution already expects: # C:\gtk-build\gtk\\release\... $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 # Provide python wrapper "glib-genmarshal" at the exact location vcxproj calls. Ensure-GenmarshalWrapper -gtkBinDir $gtkBin -gtkPrefix $gtkPrefix -wantPlat $wantPlat # Compatibility aliases (older vcxproj names) 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 (some projects still look for ssleay32/libeay32) Copy-AliasLib $gtkLib "ssleay32.lib" @('^libssl\.lib$', '^ssl\.lib$') Copy-AliasLib $gtkLib "libeay32.lib" @('^libcrypto\.lib$', '^crypto\.lib$') # libxml2 name used by older link lines Copy-AliasLib $gtkLib "libxml2.lib" @('^libxml2\.lib$', '^libxml2-2\.0\.lib$', '^libxml2.*\.lib$') # If the GTK bundle included LuaJIT headers somewhere, try to mirror them into include\ $luajitDirs = Get-ChildItem -Path $gtkPrefix -Recurse -Directory -ErrorAction SilentlyContinue | Where-Object { $_.Name -match '^luajit-2\.1$' -or $_.Name -match '^luajit$' } | Select-Object -First 1 if ($luajitDirs) { foreach ($h in @("lua.h","lualib.h","lauxlib.h","luaconf.h")) { $src = Join-Path $luajitDirs.FullName $h if (Test-Path $src) { Copy-Item $src (Join-Path $gtkInc $h) -Force } } } } # 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=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 ) rem Make sure MSVC finds GTK headers/libs via environment too 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 }}