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 }} run: | 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 # ----------------------------- # GTK3 stack (MSVC) from wingtk/gvsbuild (robust layout detection) # ----------------------------- $wantArch = if ("${{ matrix.platform }}" -eq "x64") { "x64" } else { "x86" } $headers = @{ "User-Agent" = "zoitechat-ci" "Authorization" = "Bearer $env:GITHUB_TOKEN" "X-GitHub-Api-Version" = "2022-11-28" } $releases = Invoke-RestMethod -Headers $headers -Uri "https://api.github.com/repos/wingtk/gvsbuild/releases?per_page=20" $asset = $null foreach ($rel in $releases) { $asset = $rel.assets | Where-Object { $_.name -match "(?i)^GTK3_.*_${wantArch}\.zip$" -or ($_.name -match "(?i)gtk3" -and $_.name -match "(?i)\b${wantArch}\b" -and $_.name -match "(?i)\.zip$") } | Select-Object -First 1 if ($asset) { break } } $usingLegacyGtk2 = $false if (-not $asset) { if ($wantArch -eq "x86") { # Fallback: keep 32-bit build working if GTK3 x86 isn't published. $usingLegacyGtk2 = $true Write-Host "No GTK3 x86 bundle found in recent wingtk/gvsbuild releases. Falling back to legacy GTK2 bundle for win32 to keep builds working." 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-legacy-${{ matrix.platform }}.7z if (Test-Path C:\gtk-build\gtk) { Remove-Item C:\gtk-build\gtk -Recurse -Force } & 7z.exe x deps\gtk-legacy-${{ matrix.platform }}.7z -oC:\gtk-build\gtk | Out-Null } else { Write-Host "Available assets in recent releases:" foreach ($rel in $releases) { Write-Host ("Release: " + $rel.tag_name) $rel.assets | ForEach-Object { Write-Host (" - " + $_.name) } } throw "Could not find a GTK3 $wantArch bundle in wingtk/gvsbuild recent releases." } } if (-not $usingLegacyGtk2) { $bundlePath = "deps\GTK3_Gvsbuild_${wantArch}.zip" Invoke-WebRequest $asset.browser_download_url -OutFile $bundlePath $extractRoot = "C:\gtk3-bundle" if (Test-Path $extractRoot) { Remove-Item $extractRoot -Recurse -Force } New-Item -Path $extractRoot -ItemType Directory -Force | Out-Null Expand-Archive -Force $bundlePath -DestinationPath $extractRoot # Infer the "release" root from a guaranteed artifact: gtk-3.0.lib or libgtk-3-0.dll $gtk3lib = Get-ChildItem -Path $extractRoot -Recurse -Filter "gtk-3.0.lib" -ErrorAction SilentlyContinue | Select-Object -First 1 $gtk3dll = $null if (-not $gtk3lib) { $gtk3dll = Get-ChildItem -Path $extractRoot -Recurse -Include "libgtk-3-0.dll","gtk-3*.dll" -ErrorAction SilentlyContinue | Select-Object -First 1 } if ($gtk3lib) { $gtkLibDir = Split-Path $gtk3lib.FullName -Parent $releaseDir = Split-Path $gtkLibDir -Parent } elseif ($gtk3dll) { $gtkBinDir = Split-Path $gtk3dll.FullName -Parent $releaseDir = Split-Path $gtkBinDir -Parent } else { throw "GTK3 bundle extracted, but neither gtk-3.0.lib nor libgtk-3-0.dll was found. Layout unexpected." } $gtkBin = Join-Path $releaseDir "bin" if (-not (Test-Path $gtkBin)) { throw "GTK3 release root inferred, but bin/ was not found at: $gtkBin" } # Normalize expected path: # C:\gtk-build\gtk\\release -> $platDir = "C:\gtk-build\gtk\${{ matrix.platform }}" New-Item -Path $platDir -ItemType Directory -Force | Out-Null $link = Join-Path $platDir "release" if (Test-Path $link) { Remove-Item $link -Recurse -Force -ErrorAction SilentlyContinue } New-Item -Path $platDir -Name "release" -ItemType Junction -Value $releaseDir | Out-Null # Find any glib-genmarshal in the bundle (exe or script) $genAny = Get-ChildItem -Path $extractRoot -Recurse -ErrorAction SilentlyContinue | Where-Object { $_.Name -match '(?i)^glib-genmarshal(\.exe|\.py)?$' } | Select-Object -First 1 $genTarget = Join-Path $gtkBin "glib-genmarshal" # what your vcxproj calls via python.exe if ($genAny) { $genPath = $genAny.FullName.Replace('\','\\') $pyWrapper = @( 'import os, subprocess, sys', "tool = r`"$genPath`"", 'if tool.lower().endswith(".py"):', ' sys.exit(subprocess.call([sys.executable, tool] + sys.argv[1:]))', 'else:', ' sys.exit(subprocess.call([tool] + sys.argv[1:]))' ) -join "`r`n" Set-Content -Path $genTarget -Value $pyWrapper -Encoding ASCII } else { # Fallback: install MSYS2 glib2 tools and run their glib-genmarshal with PATH set. Write-Host "glib-genmarshal not found in GTK3 bundle. Installing MSYS2 glib2 tools as a fallback." choco install msys2 -y --no-progress $msysBash = "C:\msys64\usr\bin\bash.exe" if (-not (Test-Path $msysBash)) { throw "MSYS2 install failed: bash.exe not found." } $mingw = if ("${{ matrix.platform }}" -eq "x64") { "mingw64" } else { "mingw32" } $pkg = if ("${{ matrix.platform }}" -eq "x64") { "mingw-w64-x86_64-glib2" } else { "mingw-w64-i686-glib2" } & $msysBash -lc "pacman -Sy --noconfirm --needed $pkg" $msysGen = "C:\msys64\$mingw\bin\glib-genmarshal.exe" if (-not (Test-Path $msysGen)) { throw "MSYS2 glib-genmarshal.exe not found at expected path: $msysGen" } $msysBin = (Split-Path $msysGen -Parent).Replace('\','\\') $msysGenEsc = $msysGen.Replace('\','\\') $pyWrapper = @( 'import os, subprocess, sys', "exe = r`"$msysGenEsc`"", 'env = os.environ.copy()', "env[`"PATH`"] = r`"$msysBin`" + `";`" + env.get(`"PATH`",`"`")", 'sys.exit(subprocess.call([exe] + sys.argv[1:], env=env))' ) -join "`r`n" Set-Content -Path $genTarget -Value $pyWrapper -Encoding ASCII } # Compatibility aliases while vcxproj still names GTK2 libs. $gtkLib = Join-Path "C:\gtk-build\gtk\${{ matrix.platform }}\release\lib" "" if (Test-Path $gtkLib) { $gtk3 = Get-ChildItem -Path $gtkLib -Filter "gtk-3*.lib" -ErrorAction SilentlyContinue | Select-Object -First 1 if ($gtk3 -and (-not (Test-Path (Join-Path $gtkLib "gtk-win32-2.0.lib")))) { Copy-Item $gtk3.FullName (Join-Path $gtkLib "gtk-win32-2.0.lib") -Force } $gdk3 = Get-ChildItem -Path $gtkLib -Filter "gdk-3*.lib" -ErrorAction SilentlyContinue | Select-Object -First 1 if ($gdk3 -and (-not (Test-Path (Join-Path $gtkLib "gdk-win32-2.0.lib")))) { Copy-Item $gdk3.FullName (Join-Path $gtkLib "gdk-win32-2.0.lib") -Force } # ----------------------------- # libxml2 compatibility alias (fixes LNK1181: libxml2.lib not found) # Some bundles name it libxml2-2.0.lib or xml2.lib. # ----------------------------- $xmlWant = Join-Path $gtkLib "libxml2.lib" if (-not (Test-Path $xmlWant)) { $xmlAlt = @( (Join-Path $gtkLib "libxml2-2.0.lib"), (Join-Path $gtkLib "libxml2-2.lib"), (Join-Path $gtkLib "xml2.lib") ) | Where-Object { Test-Path $_ } | Select-Object -First 1 if ($xmlAlt) { Copy-Item $xmlAlt $xmlWant -Force } else { # Last resort: search within inferred release dir $found = Get-ChildItem -Path (Split-Path $gtkLib -Parent) -Recurse -Filter "*.lib" -ErrorAction SilentlyContinue | Where-Object { $_.Name -match '(?i)^libxml2(-2(\.0)?)?\.lib$|^xml2\.lib$' } | Select-Object -First 1 if ($found) { Copy-Item $found.FullName $xmlWant -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 run: | if "${{ matrix.platform }}"=="x64" ( call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\Tools\VsDevCmd.bat" -host_arch=amd64 -arch=amd64 ) else ( call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\Common7\Tools\VsDevCmd.bat" -host_arch=amd64 -arch=x86 ) 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%" set "GTKROOT=C:\gtk-build\gtk\${{ matrix.platform }}\release" if not exist "%GTKROOT%\bin" ( echo GTKROOT bin not found at %GTKROOT%\bin dir "C:\gtk-build\gtk\${{ matrix.platform }}" exit /b 1 ) set "PATH=%GTKROOT%\bin;%PATH%" set "INCLUDE=%GTKROOT%\include;%INCLUDE%" if exist "%GTKROOT%\include\libxml2" set "INCLUDE=%GTKROOT%\include\libxml2;%INCLUDE%" set "LIB=%GTKROOT%\lib;%LIB%" rem (extra safety) ensure libxml2.lib exists even if bundle naming differs if not exist "%GTKROOT%\lib\libxml2.lib" ( if exist "%GTKROOT%\lib\libxml2-2.0.lib" copy /y "%GTKROOT%\lib\libxml2-2.0.lib" "%GTKROOT%\lib\libxml2.lib" if exist "%GTKROOT%\lib\libxml2-2.lib" copy /y "%GTKROOT%\lib\libxml2-2.lib" "%GTKROOT%\lib\libxml2.lib" if exist "%GTKROOT%\lib\xml2.lib" copy /y "%GTKROOT%\lib\xml2.lib" "%GTKROOT%\lib\libxml2.lib" ) rem Build LuaJIT (MSVC) if lua.h isn't present in the GTK bundle. if not exist "%GTKROOT%\include\luajit-2.1\lua.h" ( set "LUABASE=C:\gtk-build\luajit\${{ matrix.platform }}" if not exist "%LUABASE%\include\lua.h" ( rmdir /s /q C:\gtk-build\luajit-src 2>nul git clone --depth 1 --branch v2.1 https://github.com/LuaJIT/LuaJIT.git C:\gtk-build\luajit-src pushd C:\gtk-build\luajit-src\src call msvcbuild.bat popd mkdir "%LUABASE%\include" 2>nul copy /y C:\gtk-build\luajit-src\src\lua.h "%LUABASE%\include\" copy /y C:\gtk-build\luajit-src\src\lauxlib.h "%LUABASE%\include\" copy /y C:\gtk-build\luajit-src\src\luaconf.h "%LUABASE%\include\" copy /y C:\gtk-build\luajit-src\src\luajit.h "%LUABASE%\include\" 2>nul mkdir "%LUABASE%\lib" 2>nul copy /y C:\gtk-build\luajit-src\src\lua51.lib "%LUABASE%\lib\" ) set "INCLUDE=%LUABASE%\include;%INCLUDE%" set "LIB=%LUABASE%\lib;%LIB%" ) else ( set "INCLUDE=%GTKROOT%\include\luajit-2.1;%INCLUDE%" ) msbuild win32\zoitechat.sln /m /verbosity:minimal /p:Configuration=Release /p:Platform=${{ matrix.platform }} shell: cmd - name: Preparing Artifacts run: | move ..\zoitechat-build\${{ matrix.platform }}\ZoiteChat*.exe .\ move ..\zoitechat-build .\ shell: cmd - 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 }}