This guide explains how to version, package, and publish XpressFormula releases.
If you are publishing public Windows releases, also read the Windows signing guide:
Version values are defined in:
Key macros:
XF_VERSION_MAJORXF_VERSION_MINORXF_VERSION_PATCHXF_VERSION_BUILD
The GitHub release tag must match v<major>.<minor>.<patch>.
Packaging produces three files:
- Portable executable:
XpressFormula-<version>-x64.exe - MSI installer:
XpressFormula-<version>-x64.msi - Setup bootstrapper:
XpressFormula-<version>-x64-setup.exe
For best Defender/SmartScreen results, sign all three artifacts. See code-signing.md for the required signing order and CI snippets.
Workflow file:
The release job pins key versions with environment variables:
DOTNET_VERSION(currently9.0.x)PYTHON_VERSION(currently3.12)WIX_VERSION(currently6.0.2)MSVC_PLATFORM_TOOLSET(currentlyv145)BUILD_CONFIGURATION(currentlyRelease)BUILD_PLATFORM(currentlyx64)
MSVC_PLATFORM_TOOLSET is explicitly passed to MSBuild using /p:PlatformToolset=....
This prevents runner mismatch errors like MSB8020 when a project file was saved with an unavailable toolset.
The release build also injects XF_BUILD_REPO_URL, XF_BUILD_BRANCH, XF_BUILD_COMMIT,
and XF_BUILD_VERSION into the binary so metadata appears in the running application.
These are passed through MSBuild properties: XfBuildRepoUrl, XfBuildBranch,
XfBuildCommit, and XfBuildVersion.
The workflow currently uses windows-2025-vs2026 to get MSBuild 18.x/VS 2026 toolchain on GitHub-hosted runners.
Release packaging runs the architecture boundary check and the Release test suite before package creation. A failed boundary check, build, or test run blocks artifact upload and release publication.
Prerequisites:
- Visual Studio 2026 or Build Tools 2026 with the C++ workload and toolset
v145installed - WiX Toolset v6 CLI (
wix) - WiX Burn extension (
WixToolset.Bal.wixext) matching your WiX v6 version
The repository, CI, and local release simulation default to v145. Older Visual Studio installations can be used only by explicitly retargeting local builds to an installed toolset such as v143.
Install WiX CLI and extension:
$wixVersion = "6.0.2"
dotnet tool uninstall --global wix
dotnet tool install --global wix --version $wixVersion
wix extension add --global WixToolset.Bal.wixext/$wixVersion
wix --versionpackaging/build-packages.ps1 expects the BAL extension to already be installed and prints the exact install command if it is missing.
For WiX 6, the script also auto-detects and uses the extension DLL path when the extension cache reports the package as damaged.
Build app binary:
$solutionDir = (Resolve-Path .\src).Path + '\'
msbuild src\XpressFormula\XpressFormula.vcxproj /t:Build /m `
/p:Configuration=Release `
/p:Platform=x64 `
/p:PlatformToolset=v145 `
/p:SolutionDir="$solutionDir" `
/p:IntDir="$PWD\build\obj\" `
/p:OutDir="$PWD\build\bin\"Build and run release tests before packaging:
msbuild src\XpressFormula.Tests\XpressFormula.Tests.vcxproj /t:Build /m `
/p:Configuration=Release `
/p:Platform=x64 `
/p:PlatformToolset=v145 `
/p:IntDir="$PWD\build\test-obj\" `
/p:OutDir="$PWD\build\test-bin\"
.\build\test-bin\XpressFormula.Tests.exeCreate packages:
$version = python scripts/get_version.py
.\packaging\build-packages.ps1 `
-AppExePath .\build\bin\XpressFormula.exe `
-Version $version `
-OutputDir artifacts\releasewindows-2025-vs2026 GitHub runner behavior cannot be reproduced with Linux-based act for this workflow.
Use the local Windows simulation script instead:
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\test-release-pipeline-local.ps1Common options:
# Build only (skip WiX packaging)
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\test-release-pipeline-local.ps1 -SkipPackaging
# Override toolset or output directory
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\test-release-pipeline-local.ps1 `
-PlatformToolset v145 `
-WixVersion 6.0.2 `
-OutputDir artifacts\release-localIf you also want to verify the tag check locally:
$version = python scripts/get_version.py
$expectedTag = "v$version"
Write-Host "Expected release tag: $expectedTag"Last updated: 2026-07-21
Verified branch head: 12eaf18408be9a612bb85529ce2c0d8e1b567e12
Automated verification completed locally with Visual Studio MSBuild 18.8.2+ce25c0108 (msbuild -version: 18.8.2.30814):
- Architecture boundary check passes:
powershell -NoProfile -ExecutionPolicy Bypass -File .\tools\check-architecture-boundaries.ps1 - Debug x64 app build succeeds:
.\scripts\invoke-msbuild.ps1 -ProjectPath "src\XpressFormula\XpressFormula.vcxproj" -Configuration Debug -Platform x64 -Targets BuildOutput:src\XpressFormula\x64\Debug\XpressFormula.exe. - Release x64 app build succeeds:
.\scripts\invoke-msbuild.ps1 -ProjectPath "src\XpressFormula\XpressFormula.vcxproj" -Configuration Release -Platform x64 -Targets BuildOutput:src\XpressFormula\x64\Release\XpressFormula.exe. - Debug x64 test project builds:
.\scripts\invoke-msbuild.ps1 -ProjectPath "src\XpressFormula.Tests\XpressFormula.Tests.vcxproj" -Configuration Debug -Platform x64 -Targets BuildOutput:src\XpressFormula.Tests\x64\Debug\XpressFormula.Tests.exe. - Release x64 test project builds:
.\scripts\invoke-msbuild.ps1 -ProjectPath "src\XpressFormula.Tests\XpressFormula.Tests.vcxproj" -Configuration Release -Platform x64 -Targets BuildOutput:src\XpressFormula.Tests\x64\Release\XpressFormula.Tests.exe. - No compiler warnings in the above builds (
0 Warning(s),0 Error(s)). - Debug automated tests pass:
src\XpressFormula.Tests\x64\Debug\XpressFormula.Tests.exereported531/531 tests passed. - Release automated tests pass:
src\XpressFormula.Tests\x64\Release\XpressFormula.Tests.exereported531/531 tests passed. - Local release workflow dry run without packaging passes from a clean detached worktree at the verified head:
powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\test-release-pipeline-local.ps1 -SkipPackaging - PR Validation passes on the verified head:
run
#8, workflow run29855326680, resultsuccess. - First-party app, library, and test projects build with
/W4, conformance mode, and/Zc:__cplusplus. - Existing core tests, project/session persistence tests, export settings and metadata tests, UI layout-plan tests, formula-list action tests, update-controller tests, and plotting geometry tests pass.
- Expression runtime benchmark was not rerun for Prompt 15.1 because evaluator runtime allocation behavior did not change in this closure pass.
Prompt 15.1 smoke coverage recorded by automated tests:
- Formula input: malformed dot input, zero-valued scientific literals, true non-zero underflow/overflow, and wrong function arity.
- Shutdown: non-blocking cancellation, late-result ignore, repeated cancellation, and controller destruction while a fake fetcher is blocked.
- Projects: bounded project reads, formula-count limits, expression-length limits, serialized-size limits, rejected Save As, and old-target preservation.
- Plotting: isolated exact-zero contact, exact-grid-plane mesh, constant-zero field, duplicate triangle rejection, and deterministic Surface Nets output.
Manual verification still required before tagging:
- Interactive formula editor workflows: Add/Cancel, Add/Apply, Edit/Cancel, and live error display in the running UI.
- Interactive shutdown workflow: start a manual update check in the running app, close immediately, and confirm no visible delay or crash.
- Plotting workflows: 2D curves, discontinuities, implicit contours, heatmaps, scalar-field cross-sections, 3D explicit surfaces, implicit surfaces, multiple implicit surfaces, camera presets, auto-rotation dirty-state behavior, grid-plane interleave, axis triad, coordinates, and wire/envelope thickness minimums and maximums.
- Project workflows: New, Open valid
.xfplot, Save, Save As, Recent reopen, dirty marker set/clear, Save/Discard/Cancel before New/Open/Close, unsupported schema error, malformed file error, invalid formula warning, Unicode formula round trip, multiple save/load cycles retaining values. - Export workflows: every export profile, transparent PNG, grayscale export, metadata sidecar output, metadata JSON opens in a parser/editor, preview/final export parity, save/copy/open/reveal/copy-path, offscreen fallback behavior if reproducible.
- Responsive UI: wide/medium/compact/extra-compact toolbar, minimum/default/maximum sidebar width, narrow/short windows, formula cards at narrow widths, and 100%, 125%, 150%, and 200% Windows scaling where available.
- Application version:
- Edit
XF_VERSION_MAJOR,XF_VERSION_MINOR, andXF_VERSION_PATCHinsrc/XpressFormula/Version.h. - Create a matching Git tag (
v<major>.<minor>.<patch>).
- CI dependency versions:
- Edit
DOTNET_VERSION,PYTHON_VERSION, andWIX_VERSIONin.github/workflows/release-packaging.yml.
- MSVC toolset version used by CI:
- Edit
MSVC_PLATFORM_TOOLSETin.github/workflows/release-packaging.yml. - Ensure the selected runner image has that toolset installed, or the build will fail with
MSB8020.
- Local simulation defaults:
- Use parameters on
scripts/test-release-pipeline-local.ps1(-PlatformToolset,-WixVersion,-Configuration,-Platform,-OutputDir).
Trigger modes:
- Push tag
v*.*.*(recommended for official releases) - Manual
workflow_dispatch
Pipeline actions:
- Builds
Release|x64app binary with explicit platform toolset. - (Recommended) Signs the app EXE.
- Builds MSI and setup EXE using WiX.
- (Recommended) Signs the MSI before building/publishing the bundle, and signs the final setup EXE.
- Verifies signatures (recommended).
- Uploads artifacts.
- For tag pushes, publishes assets to the GitHub release.
- Update version in
src/XpressFormula/Version.h. - Commit changes:
git add .
git commit -m "Release v1.2.3"- Create and push tag:
git tag v1.2.3
git push origin main --tags- Wait for the
Release Packagingworkflow to complete. - Verify uploaded release assets (
.exe,.msi, and setup.exe) on GitHub.
- WiX MSI definition:
packaging/wix/Product.wxs - WiX setup EXE bundle:
packaging/wix/Bundle.wxs - Packaging script:
packaging/build-packages.ps1 - Local pipeline simulation script:
scripts/test-release-pipeline-local.ps1
This document is licensed under the MIT License. See ../LICENSE.