Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 66 additions & 9 deletions .github/workflows/windows-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ on:
branches: [master]
pull_request:
branches: [master]
release:
types: [published]
workflow_dispatch:
inputs:
installer:
Expand Down Expand Up @@ -59,9 +61,18 @@ jobs:
with:
submodules: recursive

# 3.13 to match the rest of the python plugin's pins: Python3Lib
# (python313) in win32\poxchat.props, the embeddable runtime in
# build-deps.ps1, and the python313.* names in poxchat.iss.tt. cffi is
# both a build dependency (generate_plugin.py renders python.c through
# it) and the source of the _cffi_backend.pyd that copy.vcxproj stages
# out of this interpreter's site-packages.
- uses: actions/setup-python@v5
with:
python-version: '3.12'
python-version: '3.13'

- name: Install cffi
run: python -m pip install --disable-pip-version-check cffi

# The whole C:\gtk-build tree: install prefix, intermediates and source
# archives. The intermediates belong in here because gvsbuild's
Expand Down Expand Up @@ -107,20 +118,43 @@ jobs:
if (-not $line) { throw 'no version in meson.build' }
"version=$($line.Matches[0].Groups[1].Value)" >> $env:GITHUB_OUTPUT

# Split by target so a failure names the piece that broke. perl, python3
# and htm (the C# theme manager) are left out until the rest is green:
# they need a matching Strawberry Perl / CPython / .NET on the runner.
# lua is out too, for want of a LuaJIT gvsbuild can build -- see
# build-deps.ps1.
# Split by target so a failure names the piece that broke. perl is
# dropped deliberately, htm (the C# theme manager) needs a .NET toolchain
# nobody misses -- neither ships. lua links the LuaJIT that
# build-deps.ps1 installs into the gvsbuild prefix; python3 links
# setup-python's import library and ships the embeddable runtime staged
# below.
- name: Build core
run: msbuild win32\poxchat.sln ${{ env.MSBUILD_PROPS }} /p:YourPython3Path=$env:pythonLocation /t:"poxchat\common;poxchat\fe-gtk;poxchat\fe-text"

- name: Build plugins
run: msbuild win32\poxchat.sln ${{ env.MSBUILD_PROPS }} /p:YourPython3Path=$env:pythonLocation /t:"plugins\checksum;plugins\exec;plugins\fishlim;plugins\sysinfo;plugins\winamp;plugins\upd;plugins\notifications-winrt;external\libenchant_win8"
run: msbuild win32\poxchat.sln ${{ env.MSBUILD_PROPS }} /p:YourPython3Path=$env:pythonLocation /t:"plugins\checksum;plugins\exec;plugins\fishlim;plugins\sysinfo;plugins\winamp;plugins\upd;plugins\notifications-winrt;external\libenchant_win8;scripting\lua;scripting\python3"

- name: Stage the distributable tree
run: msbuild win32\poxchat.sln ${{ env.MSBUILD_PROPS }} /p:YourPython3Path=$env:pythonLocation /t:"release\nls;release\copy"

# The embeddable package from build-deps.ps1, minus its interpreter
# binaries (scripts run inside poxchat) and its signature catalog.
# Never overwrite: the tree already stages gvsbuild's sqlite3.dll, and
# python's _sqlite3.pyd is happy with any current sqlite beside it.
# Runs before the import check and smoke test so hcpython3.dll's
# python313.dll dependency is judged against the tree that ships.
- name: Stage the Python runtime
run: |
$rel = "${{ env.BUILD_DIR }}\x64\rel"
$skip = 'python.exe', 'pythonw.exe', 'python.cat'
Get-ChildItem C:\gtk-build\python-embed -File |
Where-Object { $skip -notcontains $_.Name } |
ForEach-Object {
$name = if ($_.Name -eq 'LICENSE.txt') { 'PYTHON-LICENSE.txt' } else { $_.Name }
$dest = Join-Path $rel $name
if (Test-Path $dest) {
Write-Host "keeping staged $name"
} else {
Copy-Item $_.FullName $dest
}
}

# The smoke test below proves the tree runs, but STATUS_DLL_NOT_FOUND does
# not say which DLL is missing, so name the whole set first -- same
# reasoning as the inventory step: one runner cycle per missing file is
Expand Down Expand Up @@ -180,12 +214,35 @@ jobs:
path: ${{ steps.portable.outputs.path }}

- name: Build installer
if: inputs.installer
if: inputs.installer || github.event_name == 'release'
run: .\win32\ci\make-installer.ps1 -BuildDir "${{ env.BUILD_DIR }}" -Provision

- name: Upload installer
if: inputs.installer
if: inputs.installer || github.event_name == 'release'
uses: actions/upload-artifact@v4
with:
name: poxchat-x64-installer
path: ${{ env.BUILD_DIR }}\x64\*.exe

# Attach the Windows artifacts to the release that triggered the run, the
# same way release.yml attaches the AppImage and debs. A separate job so
# the build keeps its read-only token; only this upload needs write. A
# release run reads the dependency cache from the default branch, so tagging
# after master's own run has completed keeps this off the cold path.
publish:
if: github.event_name == 'release'
needs: build
runs-on: ubuntu-24.04
permissions:
contents: write
steps:
- uses: actions/download-artifact@v4
with:
merge-multiple: true

- name: Upload to the release
uses: softprops/action-gh-release@v2
with:
files: |
PoxChat-*-portable.zip
PoxChat-*_x64.exe
44 changes: 30 additions & 14 deletions docs/areas/windows-ci-build.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ when re-enabling one of the targets it currently leaves out.

| Piece | Does |
|-------|------|
| `win32/ci/build-deps.ps1` | Builds the whole dependency stack into `C:\gtk-build` — gvsbuild for GTK4/OpenSSL/libxml2/sqlite/libcurl/enchant/gettext, then static jansson and libwebsockets, prebuilt WinSparkle, and the CA bundle |
| `win32/ci/build-deps.ps1` | Builds the whole dependency stack into `C:\gtk-build` — gvsbuild for GTK4/OpenSSL/libxml2/sqlite/libcurl/enchant/gettext, then static jansson and libwebsockets, LuaJIT into the gvsbuild prefix, the CPython embeddable runtime, prebuilt WinSparkle, and the CA bundle |
| `win32/ci/check-imports.ps1` | Walks PE imports of the staged tree and names every DLL it is missing |
| `win32/ci/make-installer.ps1` | Generates `poxchat.iss` from its template and runs Inno Setup 5 |

Expand Down Expand Up @@ -71,19 +71,35 @@ prefix is a convenient corpus of real 32- and 64-bit PEs to test against.
Watch for PowerShell-vs-C reflexes. `for ($i = 0; $i -lt $n; $i++, $p += 40)`
is a parse error: the repeat clause takes one expression, not a comma list.

## Currently left out

- **lua** — gvsbuild builds LuaJIT by running `.\msvcbuild` through
`CreateProcess`, which cannot launch a `.bat` and finds nothing without the
extension. Taking lua back means building LuaJIT here the way jansson and
libwebsockets are built, and lgi and `--enable-gi` come with it.
- **perl, python3, htm** — need a matching Strawberry Perl / CPython / .NET on
the runner.
- **The installer.** `poxchat.iss.tt` names the lua, perl, python and
thememan payloads unconditionally, across `[Files]`, `[Components]`,
`[Registry]`, `[Run]`, `[Icons]` and the Pascal `[Code]` section. Until
those targets build, `-f installer=true` fails at Inno Setup. The portable
zip uploads before the installer step runs, so this costs nothing else.
## The scripting plugins

- **lua** builds against a LuaJIT that `build-deps.ps1` compiles itself and
installs into the gvsbuild prefix (gvsbuild's own recipe runs `.\msvcbuild`
through `CreateProcess`, which cannot launch a `.bat`, and fails identically
every time). lgi and `--enable-gi` are deliberately not built — the plugin
needs neither, they only ever provided GObject bindings to Lua scripts.
- **python3** links the import library out of setup-python's install and ships
CPython's *embeddable package*, staged whole into the tree, so users need no
system Python. The plugin is a cffi embedding: `generate_plugin.py` needs
`cffi` in the build interpreter, and at run time `_cffi_backend.pyd`
(staged out of that interpreter's site-packages by `copy.vcxproj`) must sit
on sys.path — the `.` entry in `python313._pth` provides that.

The Python minor version is pinned in four places that must move together:
`python-version` in the workflow, `Python3Lib` in `win32/poxchat.props`,
`$PythonEmbedVersion`/`$PythonEmbedSha256` in `build-deps.ps1`, and the
`python313.*` names in `poxchat.iss.tt`.

## Deliberately dropped

- **perl** — dropped as a supported plugin on Windows; it would need a
matching Strawberry Perl on the runner and on every user's machine.
- **htm / thememan** (the C# theme manager) — needs a .NET toolchain, and the
installer no longer offers it.

The installer template names only payloads the build produces, so
`-f installer=true` is expected to work; the portable zip still uploads before
the installer step, so an installer failure costs nothing else.

## Traps worth remembering

Expand Down
96 changes: 86 additions & 10 deletions win32/ci/build-deps.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,17 @@
solution's UserMacros point at:

gtk\<plat>\release gvsbuild prefix: GTK4, OpenSSL, libxml2, sqlite,
luajit, libcurl, enchant, and gettext (win32\nls
compiles the .po catalogues with its msgfmt.exe)
libcurl, enchant, and gettext (win32\nls compiles
the .po catalogues with its msgfmt.exe). LuaJIT is
built below and installed into this prefix, which is
where poxchat.props looks for lua51.lib and
include\luajit-2.1
jansson\ static jansson -- DepLibs wants jansson.lib and
copy.vcxproj ships no jansson dll
libwebsockets\ static libwebsockets, same reasoning
WinSparkle\<plat>\ prebuilt release, for the upd plugin
python-embed\ CPython embeddable runtime, staged whole into the
tree so the python plugin needs no system Python
cert\cacert.pem CA bundle, shipped into the tree as cert.pem

Nothing outside -BuildRoot is written, so this is safe to run on a developer
Expand All @@ -33,6 +38,15 @@ param (
[string] $JanssonTag = 'v2.15.1',
[string] $LibWebSocketsTag = 'v4.5.8',
[string] $WinSparkleVersion = '0.9.4',
# LuaJIT's v2.1 branch is release-less by policy, so pin a commit rather
# than the floating v2.1.ROLLING tag.
[string] $LuaJITCommit = '1ee778a4e37122d8ca7d5733c590a47dafd6b15c',
# The runtime the python plugin ships. Its minor version is load-bearing
# in three other places -- Python3Lib in win32\poxchat.props, setup-python
# in the workflow, and the python313.* names in poxchat.iss.tt -- so bump
# all four together, and the hash with the version.
[string] $PythonEmbedVersion = '3.13.9',
[string] $PythonEmbedSha256 = '91d828c2da3a029b41699e918674a0cb379c02cf20dab9c501306885f837402a',
[string] $CMakeGenerator = 'Visual Studio 17 2022'
)

Expand All @@ -42,13 +56,12 @@ $ProgressPreference = 'SilentlyContinue' # Invoke-WebRequest crawls with the pro
# gvsbuild project names, not pkg-config names. gettext is here for msgfmt.exe,
# which win32\nls compiles the .po catalogues with.
#
# luajit is absent, and with it lgi (which depends on it) and --enable-gi (which
# exists to produce lgi's typelibs). gvsbuild builds luajit by running
# '.\msvcbuild' through CreateProcess, which cannot launch a .bat file and finds
# nothing without the extension -- it fails identically every time, so the lua
# plugin is out of the first build. Re-adding it means building LuaJIT here the
# way jansson and libwebsockets are built below; the installer script also names
# lua and lgi files unconditionally, so that has to be settled at the same time.
# luajit is deliberately not asked of gvsbuild: it runs '.\msvcbuild' through
# CreateProcess, which cannot launch a .bat file and finds nothing without the
# extension -- it fails identically every time. LuaJIT is built directly below
# instead. lgi and --enable-gi (which exists to produce lgi's typelibs) stay
# out with it: the lua plugin itself needs neither, they were only ever for
# GObject bindings inside Lua scripts.
$GvsbuildProjects = @(
'gtk4',
'openssl',
Expand Down Expand Up @@ -230,6 +243,68 @@ if (Test-Path $lwsStatic) {
Copy-Item $lwsStatic (Join-Path $lwsPrefix 'lib\websockets.lib') -Force
}

Write-Step 'LuaJIT'
# msvcbuild.bat wants cl on PATH and to be run from src\. Doing the vcvars
# call and the cd inside a generated .cmd sidesteps cmd /c's quote-stripping
# rules, which would otherwise eat the spaces in the Visual Studio path.
# msvcbuild.bat does not reliably set an exit code, so the proof of success is
# the artifacts themselves, checked below. Always a full rebuild, but that is
# under a minute.
$ljSrc = Get-SourceTree "https://github.com/LuaJIT/LuaJIT/archive/$LuaJITCommit.zip" "LuaJIT-$($LuaJITCommit.Substring(0, 12))"
$vswhere = Join-Path ${env:ProgramFiles(x86)} 'Microsoft Visual Studio\Installer\vswhere.exe'
$vsRoot = & $vswhere -latest -products * -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath
if (-not $vsRoot) {
throw 'vswhere found no Visual Studio with the C++ toolset'
}
$vcvars = Join-Path $vsRoot 'VC\Auxiliary\Build\vcvars64.bat'
$ljBuild = Join-Path $srcRoot 'build-luajit.cmd'
@(
"call `"$vcvars`" || exit /b 1",
"cd /d `"$ljSrc\src`" || exit /b 1",
'call msvcbuild.bat'
) | Set-Content -Path $ljBuild -Encoding ascii
Invoke-Checked 'LuaJIT msvcbuild' { cmd /c $ljBuild }
foreach ($artifact in 'lua51.dll', 'lua51.lib') {
if (-not (Test-Path (Join-Path $ljSrc "src\$artifact"))) {
throw "LuaJIT build produced no $artifact"
}
}
# Install into the gvsbuild prefix, in the layout poxchat.props expects:
# LuaInclude points at include\luajit-2.1, lua.vcxproj links lua51.lib out of
# the shared lib dir, and copy.vcxproj stages bin\lua51.dll when it exists.
$ljInclude = Join-Path $prefix 'include\luajit-2.1'
New-Item -ItemType Directory -Force -Path $ljInclude | Out-Null
Copy-Item (Join-Path $ljSrc 'src\lua51.dll') (Join-Path $prefix 'bin') -Force
Copy-Item (Join-Path $ljSrc 'src\lua51.lib') (Join-Path $prefix 'lib') -Force
foreach ($header in 'lua.h', 'luaconf.h', 'lualib.h', 'lauxlib.h', 'lua.hpp', 'luajit.h') {
Copy-Item (Join-Path $ljSrc "src\$header") $ljInclude -Force
}

Write-Step "CPython $PythonEmbedVersion embeddable runtime"
# The python plugin is a cffi embedding: hcpython3.dll links python313.dll and
# needs a stdlib beside it at run time. The embeddable package is CPython's
# designed-for-this layout -- python313.dll, python313.zip, python313._pth
# (whose '.' entry keeps the install dir on sys.path, where copy.vcxproj also
# drops _cffi_backend*.pyd), and the stdlib's extension modules. The workflow
# stages this directory into the tree after the copy project runs.
$pyDir = Join-Path $BuildRoot 'python-embed'
$pyZip = Join-Path $srcRoot "python-$PythonEmbedVersion-embed-amd64.zip"
New-Item -ItemType Directory -Force -Path $srcRoot | Out-Null
if (-not (Test-Path $pyZip)) {
$pyUrl = "https://www.python.org/ftp/python/$PythonEmbedVersion/python-$PythonEmbedVersion-embed-amd64.zip"
Write-Host "downloading $pyUrl"
Invoke-WebRequest -Uri $pyUrl -OutFile $pyZip -UseBasicParsing
}
$pyDigest = (Get-FileHash -Path $pyZip -Algorithm SHA256).Hash
if ($pyDigest -ne $PythonEmbedSha256.ToUpper()) {
Remove-Item $pyZip -Force
throw "python embeddable zip hashed $pyDigest, expected $PythonEmbedSha256"
}
if (Test-Path $pyDir) {
Remove-Item $pyDir -Recurse -Force
}
Expand-Archive -Path $pyZip -DestinationPath $pyDir -Force

Write-Step 'WinSparkle'
$wsPrefix = Join-Path $BuildRoot "WinSparkle\$Platform"
$wsSrc = Get-SourceTree "https://github.com/vslavik/winsparkle/releases/download/v$WinSparkleVersion/WinSparkle-$WinSparkleVersion.zip" "WinSparkle-$WinSparkleVersion"
Expand Down Expand Up @@ -268,7 +343,8 @@ New-Item -ItemType Directory -Force -Path $certDir | Out-Null
Invoke-WebRequest -Uri 'https://curl.se/ca/cacert.pem' -OutFile (Join-Path $certDir 'cacert.pem') -UseBasicParsing

Write-Step 'done'
Write-Host "gvsbuild prefix: $prefix"
Write-Host "gvsbuild prefix: $prefix (LuaJIT installed into it)"
Write-Host "jansson: $janssonPrefix"
Write-Host "libwebsockets: $lwsPrefix"
Write-Host "WinSparkle: $wsPrefix"
Write-Host "python runtime: $pyDir"
3 changes: 2 additions & 1 deletion win32/ci/make-installer.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
separate, skippable step.

poxchat.iss.tt targets Inno Setup 5 and #includes idp.iss from the Inno
Download Plugin (which fetches vcredist/perl/python at install time). The
Download Plugin (which fetches vcredist and the spelling dictionaries at
install time). The
hosted runners ship Inno Setup 6 and no idp, so -Provision installs both:
IS5 from jrsoftware, idp from the mirror hexchat kept on its gvsbuild
releases, the upstream host having gone away years ago.
Expand Down
Loading